TECH PLAY

電通総研

電通総研 の技術ブログ

841

はじめに こんにちは。今回の記事は以下の新卒1年目の2人による共同執筆となります。 クロス イノベーション 本部 エンジニアリングテクノロ ジー センター 大岡叡 HCM事業部 製品企画開発室 基盤開発グループ  伊藤真 幸 休日に私たち2人でBedrock勉強会を行いました。大岡がBedrock Flowsを、伊藤がナレッジベース、ガードレールをそれぞれ担当し、調査・検証した内容をお互いに共有しました。この記事ではその内容をお届けします。 想定読者は「Bedrockでモデル呼び出しの経験はあるが、各種機能についてはあまり知らない方」です。新人の システム開発 研修でもBedrockやVertex AIを活用するチームが多くありましたが、基本的なモデル呼び出しにとどまるケースがほとんどでした。この記事を通じて、Bedrockへの理解が深まるきっかけになれば幸いです。 はじめに ナレッジベース 概要 検証内容 気付き ガードレール 概要 検証内容 気付き Bedrock Flows 概要 検証内容 気づき まとめ ナレッジベース 概要 ナレッジベースとは、 企業独自の情報(社内文書やFAQ等)を基にした回答の生成(RAG)の実装を簡素化 する、 Amazon Bedrockの機能です。 以下がナレッジベースのイメージ図になります。 AWS公式ブログ より引用 ナレッジベースは以下3つの項目を最低限設定することで作成できます。 S3やRedshift等にデータ(.pdf、. csv 等)を配置する データを生成AIが利用しやすい形(ベクトルデータ)に変換するための埋め込みモデルを選択 ベクトルデータを保存するためのベクトルデータベースを選択 今回初めてS3を触ったような AWS 初学者の私でも、ナレッジベースを利用することでRAGを簡単に実装 することができました。 検証内容 検証として、データベースに登録されている社員情報(テストデータ)をAIモデルとの対話形式で簡単に検索できるナレッジベースを作成し、 AWS 上での動作テストまで実施しました。 【ナレッジベースの作成手順】 社員情報のファイルを csv 形式で作成し、S3の汎用 バケット にアップロード 社員情報の例) 項目 値 社員番号 E001 名前 山田太郎 部署 営業部 役職 部長 年次 15 所属拠点 東京本社 メールアドレス yamada.taro@ example.com 電話番号 03-1234-5678 住所 東京都 千代田区 丸の内1-1-1 雇用区分 正社員 在籍ステータス 在籍中 上長社員番号 EX001 スキル 営業戦略・顧客管理・プレゼンテーション 情報区分 一般 閲覧レベル レベル3 入社日 2010-04-01 クレジットカード番号 4111111111111111 有効期限 2026-12 備考 優秀な営業成績を持つベテラン社員 Amazon Bedrockのナレッジベースから「作成」を押下し、ベクトルストアを含むナレッジベースを選択 データソースにS3を使用 S3の URI に、1.で配置したデータの URI を設定 埋め込みモデルにTitan Text Embeddings V2を使用 ベクトルストアに Amazon OpenSearch Serverlessを使用 上記以外はデフォルト設定で、ナレッジベースを作成 作成したナレッジベースを選択し、データソースを同期 以上で、ナレッジベースの作成が完了しました。 ここから、ナレッジベースのテストを行っていきます。 今回の検証では、生成AIモデルは Amazon Nova Liteを使用しました。 ナレッジベースに「役職が課長以上の社員の名前を教えてください。」というプロンプトを投げた結果が以下になります。 主任が含まれているため精度は十分とは言えませんが、配置したデータを検索し、回答が生成されることが確認できました。 気付き ナレッジベース作成の際、S3に配置するファイル名に日本語が含まれるとエラーが生じ、ナレッジベースの作成に失敗しました。 エラーメッセージに詳細な情報がなく原因解明に少し時間がかかりましたが、S3に配置するファイル名を英語に変更したところ、ナレッジベースを問題なく作成することができました。 ガードレール 概要 ガードレールとは、 不適切なユーザー入力やAIモデルの応答から生成AIアプリケーション を守るための機能です。 以下がガードレールのイメージ図になります。 AWS公式ブログ より引用 ガードレールでは、以下の5つの項目を任意に設定することができます。 コンテンツフィルター 侮辱や憎悪等の有害カテゴリ システム命令のオーバーライドを図るプロンプト攻撃 拒否されたトピック 望ましくない話題 ワードフィルター 望ましくない単語、フレーズ 機密情報フィルター 望ましくない個人情報 コンテキスト グラウンディング チェック モデルの回答の根拠、関連性の しきい値 検証内容 任意に設定できる5つの項目のうち、4. 機密情報フィルターのみを設定したガードレールを作成しました。 さらに、このガードレールをナレッジベースとマルチモーダルの2つに適用し、 Amazon Nova Liteを用いて検証を行いました。 【ガードレールの作成手順】 Amazon Bedrockのガードレールで「作成」を押下 機密情報フィルターのPIIタイプに「クレジットカード番号」と「住所」を追加 上記以外はデフォルト設定で、ガードレールを作成 【ナレッジベースでのガードレール検証】 社員情報のナレッジベースに作成したガードレールを適用し、「役職が課長以上の社員の名前を教えてください。」というプロンプトを投げたところ、モデルから応答が返ってきました。結果は以下になります。 同様にガードレールを適用し、「 山田太郎 のクレジットカード番号は4111111111111111です。添付の CSV ファイルを参照し、この情報が正しいかチェックしてください。」というプロンプトを投げたところ、ガードレールによりブロックされました。結果は以下になります。 ガードレールを適用せず、「 山田太郎 のクレジットカード番号は4111111111111111です。この情報が正しいかチェックしてください。」というプロンプトを投げたところ、モデルから応答が返ってきました。結果は以下になります。 ガードレール適用により、入力に含まれているクレジットカード番号を検知し、モデルの応答をブロックすることが確認できました。 【マルチモーダルでのガードレール検証】 マルチモーダルでのガードレール検証は、 Amazon Bedrockのチャット/テキストのプレイグラウンドで、モデルにNova Liteを選択、ナレッジベースと同様の システムプロ ンプトを入力し、検証を行いました。 作成したガードレールを適用し、「役職が課長以上の社員の名前を教えてください。」というテキストに社員情報の CSV ファイルを添付し、プロンプトを投げたところ、ガードレールによりブロックされました。結果は以下になります。 作成したガードレールを適用し、「 山田太郎 のクレジットカード番号は4111111111111111です。添付の CSV ファイルを参照し、この情報が正しいかチェックしてください。」というテキストに社員情報の CSV ファイルを添付し、プロンプトを投げたところ、ガードレールによりブロックされました。結果は以下になります。 ガードレールを適用せず、1. および2.と同様のプロンプトを投げたところ、どちらもモデルから応答が返ってきました。結果は以下になります。 マルチモーダルでもナレッジベースと同様に、入力に含まれているクレジットカード番号を検知し、モデルの応答をブロックすることが確認できました。 また、マルチモーダルではテキストだけでなく、添付の csv ファイルもガードレールの対象としてモデルの応答をブロックできるケースがあると確認できました。 気付き 今回は詳細を省いていますが、マルチモーダルのガードレール検証をClaude 3.5 Sonnetで実施したところ、Nova Liteとは違う結果になりました。Nova Liteでは「役職が課長以上の社員の名前を教えてください。」というプロンプトに対してモデルからの応答がブロックされましたが、Claude 3.5 Sonnetでは応答がブロックされませんでした。 AIモデルによって添付ファイルの展開の仕方が違うなどの原因が考えられるため、今後さらに調査・検証をしていきたいと思います。 Bedrock Flows 概要 Bedrock Flowsとは、 ローコードでワークフローを構築できるツール です。類似サービスで言うと、Difyなどが挙げられます。 Bedrock Flowsについて AWS 公式サイトでは以下のように説明されています。 Amazon Bedrock Flows では、ノードを接続して生成 AI ワークフローを構築できます。各ノードは、 Amazon Bedrock または関連リソースを呼び出すフローのステップに対応します。ノードへの入力とノードからの出力を定義するには、式を使用して入力の解釈方法を指定します。 ( AWS公式サイト より引用) どのように構築できるかというと、以下のようにノードを線でつないでワークフローを構築できます。 執筆時点(2025/11)で利用できるノードは以下のとおりです。 【フローロジックを制御するノード】 ノードタイプ 概要 Flow Input フローの開始点。InvokeFlowリクエストからデータを受け取る Flow Output フローの終了点。結果を返す Condition 条件に応じて処理を分岐 Iterator 配列の各要素を順次処理 Collector Iteratorの結果を配列として収集 DoWhile 条件が満たされるまでループ処理を実行 【フロー内のデータを処理するノード】 ノードタイプ 概要 Prompt 定義されたプロンプトでモデルを呼び出しレスポンスを生成 Agent エージェントを呼び出してタスクを実行 Knowledge Base ナレッジベースからデータを取得 S3 Storage S3 バケット にデータを保存 S3 Retrieval S3 バケット からデータを取得 Lambda Function Lambda関数を呼び出してカスタムロジックを実行 Inline Code フロー内で直接コードを実行 Lex Lexボットで発話を処理し インテント を識別 検証内容 Bedrock Flowsの検証として、カスタマーサポート回答システムを実装しました。その概要と動作結果を報告します。 本検証では以下のようなフローを実装しました。ユーザーの問い合わせに対して問い合わせカテゴリーを識別し、ナレッジベースを呼び出してS3にあるドキュメントから回答に役立ちそうな情報を取得し、最後にナレッジベースの出力とユーザーの問い合わせ内容を組み合わせて、適切な回答を出力する、これがワークフローの全体図です。 続いて、ワークフローのノードごとの処理について以下で説明します。 InputNode ノードタイプ:Flow Input 役割:顧客の問い合わせを受ける 出力:問い合わせテキスト(String) QuestionClassifierNode ノードタイプ:Prompt 役割:問い合わせのカテゴリーを識別する モデル: Amazon Nova Pro 入力:問い合わせテキスト(String) 出力:問い合わせカテゴリー("technical", "billing", "general" のいずれか) プロンプト: あなたは顧客からの問い合わせを分類する専門家です。以下の問い合わせ内容を分析し、最も適切なカテゴリを1つ選択してください。 問い合わせ内容: {{inquiry}} 【カテゴリの判定基準】 **technical** - 技術的な問題やシステムの使い方: - ログイン、パスワード、認証に関する問題 - アプリケーションのインストール、設定、アップデート - エラーコード、エラーメッセージ - システム要件、動作環境 - API、データバックアップ、セキュリティ設定 - 動作が遅い、フリーズする等のパフォーマンス問題 - 機能の使い方、操作方法 **billing** - 料金・請求・支払いに関する問題: - 請求、支払い、決済、料金 - プラン変更、アップグレード、ダウングレード - 領収書、請求書の発行 - 返金、キャンセル、無料トライアル - クレジットカード情報の更新 - 請求サイクル、支払い方法 **general** - サービス全般やアカウント管理: - サービスの概要、機能説明 - アカウント登録、初期設定 - サポート窓口、営業時間の問い合わせ - プライバシーポリシー、利用規約 - 対応言語、モバイルアプリ - 解約、アカウント削除 - その他、上記に該当しない一般的な質問 【回答形式】 以下のいずれか1つのみを出力してください(説明や追加テキストは不要): - technical - billing - general CategoryCondition ノードタイプ:Condition 役割:問い合わせのカテゴリーから移動するナレッジベースを決定する 入力:問い合わせカテゴリー("technical", "billing", "general" のいずれか) 条件: カテゴリーが"technical"ならTechnicalKnowledgeBaseノードに移動 カテゴリーが"billing"ならBillingKnowledgeBaseノードに移動 その他はGeneralKnowledgeBaseノードに移動 Knowledge Baseノード(3つ - 条件分岐で1つのみ実行) TechnicalKnowledgeBase ノードタイプ:KnowledgeBase 役割:問い合わせの回答に必要なS3に配置されている技術FAQファイルの情報を検索する 入力:問い合わせテキスト(String) 出力:ナレッジベースの検索結果(String) BillingKnowledgeBase ノードタイプ:KnowledgeBase 役割:問い合わせの回答に必要なS3に配置されている請求FAQファイルの情報を検索する 入力:問い合わせテキスト(String) 出力:ナレッジベースの検索結果(String) GeneralKnowledgeBase ノードタイプ:KnowledgeBase 役割:問い合わせの回答に必要なS3に配置されている一般FAQファイルの情報を検索する 入力:問い合わせテキスト(String) 出力:ナレッジベースの検索結果(String) ResponseFormatterノード(3つ - 各KnowledgeBase専用) ノードタイプ:Prompt 役割:問い合わせ内容とナレッジベースの検索結果から最終的な回答を生成する モデル: Amazon Nova Pro 入力: 問い合わせテキスト(String) ナレッジベースの検索結果(String) 出力:最終的な回答(String) プロンプト(カテゴリーが"technical"の場合): あなたはカスタマーサポート担当者です。お客様からの技術的な問い合わせに対して、技術サポートナレッジベースから検索結果を取得しました。 この検索結果を基に、お客様に分かりやすく丁寧な回答を作成してください。 お客様の問い合わせ: {{inquiry}} ナレッジベースの検索結果: {{kb_result}} 【回答作成のガイドライン】 - 段階的な手順を明確に説明 - 検索結果に含まれる情報を正確に伝える - 手順がある場合は番号付きで提示 - 検索結果がない場合は、サポート窓口への案内を含める 丁寧で分かりやすい回答を日本語で作成してください: OutputNode (3つ - 各パス専用) ノードタイプ:Flow Output 入力:最終的な回答(String) このフローをマネジメントコンソールでテストしました。その結果を以下に示します。詳細は省きますが、適切に回答をカテゴリー分類してナレッジベースから情報を取得し、回答を生成できることを確認しました。( エイリアス を作成することでクライアントアプリケーションから呼び出すことも可能ですが、今回はそこまでやっていません。) 気づき terraform apply は通るが、マネジメントコンソールでフローのテストをするとエラーが発生するケースがあること (前提として、今回はTerraformでフローを実装しました。) 例えば、以下のような仕様に反する設定があっても terraform apply は通り、マネジメントコンソールでテストするとエラーが発生します。 Prompt ノードの入力に複数ノードの出力を接続 Condition ノードに「else」に相当する条件(default条件)が設定されていない 今後、TerraformでBedrock Flowsを実装する時はこの点に注意しようと思いました。 AWS Provider の GitHub リポジトリ で Bedrock Flows の実装を確認した結果、フロー作成・更新時には CreateFlow 、 GetFlow 、 UpdateFlow の API を呼び出す仕様であることが分かりました。よって、これらの API ではフローが仕様に沿っていなくてもエラーを返さないことがあると今回の検証で分かりました。 まとめ 二人でBedrockの機能について調査・検証した内容をご紹介しました。 この記事の執筆を通じて、ナレッジベース、ガードレール、Bedrock Flowsを実際に使ってみて理解を深めることができました。 ここまでお読みいただきありがとうございました。 皆さんのBedrockへの理解が深まるきっかけになれば幸いです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @ooka.toru レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
こんにちは、 電通 総研 コーポレート本部 サイバーセキュリティ推進部の櫻井です。 本記事ではISC2 CCSP試験を紹介します。 なお、本記事でご紹介する資格の情報は2025年7月時点のものとなります。 ISC2 CCSP試験とは? CCSPは正式名称Certified Cloud Security Professionalであり、ISC2(アイ エス シーツー)が認定している クラウド サービスを安全に利用するために必要な知識を体系化した資格となります。 クラウド 上でのインフラ設計だけでなく クラウド がどのように構成されているのかや クラウド 上のアプリケーション設計に関する要素も含まれていますので、一般的な クラウド 関連の試験とは要素が異なります。 ※1 CCSP®とは 試験向けに利用したコンテンツや学習期間 筆者のキャリア オンプレ& クラウド 、ネットワーク&セキュリティ等いろいろな分野に関わってきたエンジニアです。担務領域に関しては特にこだわりなくなんとなく興味を持った資格は取得してます。(※ My credly) 利用したコンテンツ CCSPは2015年に提供開始(※2)していますが、同じくISC2が提供している CISSP 向けのコンテンツと比べると全体的に量は少ないです。 筆者が利用したものは以下となります。 市販の書籍として以下の二つが提供されており、両方とも利用しました。どちらも数少ない日本語で提供されているドキュメントですので、利用することをお勧めします。ただし、公式問題集については問題を解く観点や視点が重要であり、全く同じ問題は出題されないと考えた方が良いです。 CCSP CBK 公式ガイドブック(※3) 日本語ドキュメントとしてよく整理されていますが、かなりのボリュームがあるので辞書代わりとして活用しました。 CCSP 公式問題集(※4) 一通り解いてみることをお勧めします。 Udemyのコンテンツでは こちら のコンテンツを翻訳で利用しましたが、比較的に高額なのでセールのタイミングで購入するのがお勧めです。Udemy上でも英語のものであれば、それなりに数が存在します。 主に日本語翻訳後の教材を利用しましたが、英語版の原文で学習することが理想的だと思います。 時間に余裕がある方はISC2サイト内の学習コンテンツや市販されている「ISC2 CCSP Certified Cloud Security Professional Official Study Guide」をセルフ翻訳やAI翻訳を活用してじっくり学習しましょう。 ※2 ISC2の体制と沿革 ※3 CCSP CBK 公式ガイドブック ※4 CCSP 公式問題集 学習期間 期間は2か月、50時間程度の学習で試験に臨みました。 筆者の場合はバックグランドとなるインフラ、 クラウド 、ガバナンスといった学習を省いていますので、ISC2の出題方式に関する理解と既存知識とのギャップを埋めるために要した時間となります。 CCSP ドメイン 別の要素技術 CCSPの範囲は クラウド 以外の領域も含むためそれなりの広さがあります。ただし、 ドメイン に関しては一般的な技術、管理領域との対応が取れており、項目別に何を学習すればよいかはわかりやすいと思います。 筆者視点ではありますが、一般的な クラウド エンジニア視点で比較的難易度が高いのは ドメイン 3と ドメイン 4 と考えています。また、 ドメイン 6 は一般的な開発者、設計者からは遠い分野ですのでかなりの方が初めて触れるコンテンツとなる可能性が高いです。 ドメイン 3は クラウド プラットフォームの構成要素について理解する必要があります。すなわち、 クラウド を構成しているオンプレミスの機器、物理インフラ、仮想化レイヤ、ゲストOSレイヤすべての理解が必要です。 ドメイン 4は クラウド アプリケーションは クラウド 上で稼働させるアプリケーションを主題としていますが、従来のアプリケーションの開発手法やテスト技術に関する理解が必要です。 ドメイン 6で出題される法律に関してはグローバルもしくは米国の法律となるために日本国内の法律とのギャップを埋める必要が生じます。 総じて既に クラウド ベンダー資格を取得している方が問題をみると、かなりの割合の問題に違和感を感じるかもしれません。違和感を感じた方は、まずはITインフラとは何か?開発・設計とは何か?と システム開発 に関する基礎的な学習を行うことをお勧めします。 筆者の感覚ではCCSPは各領域の積み上げが必要な認定試験であり、 クラウド とは何で構成されているかを理解したうえで クラウド の安全な利用法と クラウド セキュリティの実装方法に関する知識を問う試験だと思います。 (※5、※6) # ドメイン 名 概略 (筆者のまとめ) 1 クラウド の概念、 アーキテクチャ 、設計 クラウド の概念、設計原則 2 クラウド データセキュリティ データセキュリティの概要と要素技術(暗号化等)、情報保護 3 クラウド プラットフォーム & インフラセキュリティ クラウド の構成要素、オンプレデータセンターを含む物理および理でのセキュリティコン トロール 4 クラウド アプリケーション セキュリティ アプリケーションの開発、設計手法、およびSDLC 5 クラウド オペレーション クラウド の運用、サプライヤ管理 6 クラウド ガバナンス - 法律、リスク、 コンプライアンス クラウド の法的要件、データ フォレンジック や監査 ※5 CCSP CBK 6ドメイン概要 ※6 CCSP ドメインガイドブック 試験自体の個人所感(試験予約、試験当日の動き、時間配分と感想) 試験予約 筆者はISC2の試験は初めてでしたのでISC2サイトのメンバーページのアカウント登録から始める必要がありました。(アカウント登録だけなら無料) アカウント登録については氏名、メールアドレス等の基本情報を入力で問題ありません。特に詰まるポイントはないと思います。 (注意)住所については合格後に認定証が届くため、ちゃんと入力しましょう。(※7)筆者の場合は米国からの配送となっていました。認定証と一緒にピンバッジも届きます。 その上でCCSP試験の予約を行います。 ISC2サイトでCCSPの受験チケットを購入した後、ピアソンVueのサイトで予約を行いますが、従来のCBT方式の試験とは異なり、テストセンタが PPC (Pearson Professional Centers ※8)に限定されています。主に関東、関西以外に在住の方はスケジュールに注意が必要です。 予約可能な試験枠は確認できる限りは午前(08:00)・午後(14:00)の2枠となっており、私の都合の良い日程が午前の枠だけでしたので午前の枠で申し込みを行いました。 ※7 英語での住所の書き方と注意点! ※8 ピアソン・プロフェッショナル・センター 試験当日の動き 試験日は7月上旬、8:00開始の試験でしたのでいつもよりかなり早起きをしました。新宿会場の最寄り駅は 新宿駅 ではありませんので極端に込み合うことはありませんが、早めに会場入りすることをお勧めします。 時間配分と感想 計125問を解き終えて、15分程度を残して終了しました。 以下は筆者の感想です。 事前に公式問題集等でかなりの分量を解きましたが、傾向としては似通った問題はありつつも、全く同じ問題は出題されませんでした。 事前のイメージ通り、 クラウド だけやってきた方には難しく、インフラ・アプリを含め各レイヤの技術を理解できている方は比較的楽かなと感じました。 試験は180分で125問の問題を解きます。注意する点は後から見直しを行うことができませんので、全体の時間配分に注意です。 日本語試験を選択しましたが、英字版の問題文も併せて表示されます。筆者が見た問題の中では日本語翻訳後の表現に困る問題はありませんでしたが、試験中に困った場合には英字版を確認しましょう。 (注意)2025年10月1日より、Computerized Adaptive Testing(CAT)形式の試験がCCSPにも導入されており、筆者が受験した時点と変わっておりますのでご確認ください。 Computerized Adaptive Testing(CAT)試験形式更新のお知らせ – CC、SSCP、CCSP CBT試験合格からISC2メンバー登録に至るまでのフロー概略 登録 CCSPを含むISC2試験の合格後は認定をActivateするためISC2 正規メンバーへの登録を行う必要があります。 試験を受けたのが7月上旬、筆者のcredly上でのCCSP Activate Dateとかなりのずれがあります。このように初回の正規メンバー登録については数か月を要することもありますので、早めに登録フローを実施することを推奨します。 正規メンバー登録しないとcredly上もActivateされません。 登録に必要な情報 登録作業はかなりの準備(※9)が必要です。ざっと登録に必要な情報をまとめてみました。 登録に必要なメールについては合格後にISC2から送付されてきます。直接メール内のリンクを開く感じです。メンバーページへログインしてから確認します。 まず、認定要件(※10)の条件をクリアするための情報を準備する必要があります。 「ITに関する業務に従事した経験が5年以上あること」については筆者の場合は 電通 総研在籍期間が約2年ですので5年の期間規定を満たすために前職の経歴を分けて記載しました。同一企業のみに在籍している方はこの点は楽かなと思います。 「CCSPの6 ドメイン のいずれかに関する業務が1年以上あること」ですのでそれを満たすように経歴(Job History )を入力する必要があります。 上司(supervisor)の連絡先も入力する必要がありますが、個人の連絡先でなくても大丈夫のようです。筆者は前職のsupervisorを アサイ ンできなかったため、ISC2担当者に確認を取り、在籍確認が取れる人事部を連絡先としました。必要な方はISC2担当者に確認をしましょう。 登録申請にあたってはRequest Endorsementでendorser(すなわち既存のISC2 正規メンバー)のID入力求められますが、知り合いがいないとNGではないようです。その場合Job History を保証するための エビデンス や連絡に関して確認される模様です。幸運にも筆者は同部署に複数名のISC2メンバーが在籍していましたのでendorserを依頼しました。 上記までをすべてそろえたうえで、申請を実施します。 筆者の場合は審査終了まで1か月程度要し、完了のメールが届いた段階で年会費(AMF: Annual Maintenance Fees)135米ドルの支払いを行いました。 ※9 認定手続き ※10 認定要件 最後に 読了していただきありがとうございました。ベンダー ニュートラ ルな資格としては有名なCCSPですが、3大ベンダー( AWS 、 Microsoft Azure、 Google Cloud)の資格とは求められる知識が異なるため、各 ドメイン の対策を行うことが必要です。CCSPへの学習を通して、データセンターのインフラや クラウド ガバナンスに興味を持っていただけるエンジニアが増えることを期待しています。 執筆: @sakurai.ryo レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました ) 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト
こんにちは!クロス イノベーション 本部 AIトランスフォーメーションセンターの村本です! 普段は 電通 総研で自社開発をしている生成AIプロダクト「 Know Narrator 」の開発に携わっております。今回は、私たちKnow Narrator開発チームが、普段どのように開発作業に取り掛かっているか紹介します! Know Narratorの開発メンバーによる開発関連コラムは以下にもありますので、合わせてご覧ください。 Vibe Coding(バイブコーディング)会を開催しました | AITC | AI TRANSFORMATION CENTER GitHub Copilot Coding Agentをプロダクト開発で使ってみて | AITC | AI TRANSFORMATION CENTER AITC働き方紹介 vol1 AIプロダクト開発・中堅 | AITC | AI TRANSFORMATION CENTER 株式会社 電通総研 新卒採用サイト Teams開発部屋とは? タイトルにある「Teams開発部屋」とは、単純にTeamsで作成できる会議ルームのことです。チャンネルに紐づける形で、平日の作業中開きっぱなしの会議ルームを作っています。この会議ルームのことを「開発部屋」と言っています。 Know Narratorの開発メンバーは、 MTG などの時間を除き、基本的にこの作業部屋に入った状態で開発に取り組んでいます。わからないことや、話したほうが早いことがあれば、すぐに会話できる環境を用意しているといった形です。 私たちは、いわゆるハイブリッドワーク的な働き方をしているため、このような運用をしております。リモート中心で働くメンバーもいますし、出社中心で働くメンバーもいます。また、開発にはパートナー会社の方にも協力していただいており、コミュニケーションを円滑にとるための工夫として取り入れている仕組みになります。 開発部屋のメリット この開発部屋によるメリットはいろいろあります。 話したいことをすぐに話せる 人が話しているのを聞いて、実装のコツを知ることができたり新たな発見を得ることができたりする 開発部屋のスレッドで開発に関するチャットを行うことで、やりとりを一元管理できる 1点目は想像しやすいかと思います。 2点目に関しては、対面で働いていたら発生していた「盗み聞きからの発見」みたいなものをハイブリッドワークでも実現できたということです。出社していると、隣で会話している内容が耳の中に入ってきて、参考になることがあると思います。開発部屋でも、自分以外のメンバーが仕様に関する相談をしていたり、コードレビューに関する会話をしていたりします。このような会話を聞くだけでも、学べることはたくさんあると思います。これは明確なメリットです。ほかにも、話している内容を知っているメンバーが会話に入っていく、みたいな光景もよく見られます。 3点目に関しては、Teamsではチャンネルに会議を紐づけることで、以下のようにチャンネル上のスレッドとして会議チャットを管理できるようになります。 こちらの会議チャット上で開発に関する基本的なチャットのやり取りを行うことで、「どこのスレッドでチャットすれば?」みたいな些細な悩みをなくすことができます。 例えば、「トピックごとにスレッドを立てましょう」というルールを作れば、「この内容のレベルでスレッド立てる?」という悩みや、「この内容は複数トピックまたぐのだがどこでチャットしよう」という悩みが生まれるでしょう。 私たちの開発部屋スレッドでは、PRを作成した際のレビュー依頼などが飛んでいます。この運用ならば、開発部屋に入っている全員がこのチャットを認識できるので、お互いの進捗を把握することもできます。 開発部屋の運用で気を付けていること つなぎっぱなしは気を遣ってしまう、大変そうだなぁと思う方もいるかもしれません。この部分の対策としては、基本的にマイク・カメラともにOFFにし必要なときだけマイクをONにする。PCの前からいつ離れてもらってもOKという形をとっています。集中したいから一時的にイヤホンを外す、などもOKです。 一応離席する際に「離席」やら「riseki」やらコメントを入れる習慣はあります(risekiは変換が面倒くさかったときに送りがち)。これは「そこにいると思って話しかけたら、実は離席中で返事がなかった」――そんなさみしい思いをしないための習慣だとか、そうでないとか。 また、この開発部屋では会議ツールによくある ブレイクアウト ルームの機能を利用して、自由に出入りできる「開発小部屋」も用意しています。個別の内容や、「全体に聞こえる形で話すのはちょっとなぁ」と思うときなどに利用しています。 このように、開発部屋に入ることが何らかの制限にならないようには気を付けて運用しています。 定期的に開発部屋を作り直し、名前を付けるように 毎日使うところだからこそ愛着を こんな開発部屋ですが、今年の6月くらいまで、ずっと同じ会議ルームを使い続ける運用にしていました。さらに会議名も「開発部屋」とだけ。 私個人的には、「毎日開発メンバーが入ってくる場所なのに少し無機質だなぁ」と感じていました。いかにも作業的な感じがしたのです。 そこで、 定期的(2スプリント毎に)に開発部屋を作り直す! 開発部屋(Teams会議)名にテーマを付けて、開発小部屋( ブレイクアウト ルーム)にも部屋名を付ける! という2つの運用を取り入れました。 上図にもありましたが、実際に以下のような開発部屋が立ち上がっています。 ちょうど開発部屋を作るころに寒くなってきたのと、大量の実装タスクが積まれていたのでこのようなテーマになっています。特定の機能の実装が大きなタスクとなっているときは、その機能に関連するテーマを付けたりします。 開発小部屋はこんな感じ。 過去には「松」、 「竹」 、「梅」で名づけたり、「ブラジル」、「オランダ」のように国名で名づけたりしていました。最近では時期に合わせたものにすることが増えています。 完全に内輪ノリといえば内輪ノリです。ですが、楽しく開発にかかわることは、プロダクト開発・チーム開発において最も大事だと思っているので、内輪ノリもある程度はよいのかと。些細なことかもしれませんが、日ごろから意識していきたいところです。 「○○の話をしたいので “ブラジル” に行きましょうか」 最近の開発部屋内でよく出てくるコメントです。もちろん、「開発小部屋に移って会話しましょうか!」という意味ですが、すこしマイルドな感じがします。感じがするだけです。 旅館やおしゃれなオフィスで、部屋に名前がついてたりするのをイメージしていたりはします。開発チームのメンバーも、この運用を始めた当初は少し恥ずかしそうに「ブラジルで」と話していましたが、最近では普通に話すようになりました。 名前を付ける前は、以下のようなチャットが流れていた開発チームも… 今では以下のような形に 一部のメンバーは「次の名前は何かな」という楽しみを持っているとかいないとか。 定期的に立て直すことでリズムが生まれた これは意図していなかったのですが、開発部屋を定期的に立て直すことで、時間の流れを意識できるようになりました。どういうことかというと、新しく会議ルームが立ち上がることでスレッドも一新されますし、「2スプリント経過した」ということを明確に感じるようになったのです。 Know Narratorはほぼ月一回のアップデート(リリース)を行っています。10月のアップデートが終わっても、すぐに次の11月アップデートに向けて動き出します。このように開発が継続的に続いているのです。この性質に加え、今までは毎日変わらない会議部屋に入っていたことから、ずーっと変化のない感覚があったのかもしれません。 毎日入る会議部屋が定期的に新しくなることで、気分 もリフ レッシュしたかのような感覚を得ることができました。開発部屋のテーマや開発小部屋の名前が、そのときどきに合わせたものになることで、無機質になりがちだった部分に変化を出せたのかなと。 まとめ 内輪ノリに近い形で始めた運用ですが、円滑にコミュニケーションをとること、開発が楽しいと思える環境を作ることは、良い開発チームであるために必要なことだと思っています。 また、Teamsを活用した開発部屋の運用は、チャットツールの運用という面においても比較的効果的な面があるかと思います。スレッドが乱立してどこでチャットしたらいいのかわからない。チャットを見逃してしまう。という課題にもある程度の対策にはなると思います。 この運用も今後ずっと続くとは思っていません。今後もそのときにいる開発メンバーの特性に合わせたスタイルで開発チームの運営をしていきたいですね! 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @naoki.muramoto レビュー: @yamada.y ( Shodo で執筆されました )
こんにちは。 エンタープライズ 第一本部 戦略ソリューション 1 部の英です。 普段はWebアプリや スマホ アプリの案件などを担当しています。あと、趣味でAIを勉強しています。 最近寒いですね。キーボードの下にデスクヒーターを配置したら QOL が爆上がりしたので、是非みなさんも試してみてください。 キーボードを打つ手がポカポカです。では、ポカポカの手で記事を書いていきます。 つい最近、 Amazon のBIツールである Amazon Quick Sightが Amazon Quick Suite にリ ブランディング されました。 東京リージョンではまだ新機能の一部が解放されていないのですが、新しいデザインになっているようなので触ってみましょう。 そして今回は AI駆動開発 の要素も入れてみましょう。 人が頭を使うこと、手を動かすことは最小限 にして AI駆動データ分析基盤 をサクッと構築してみます。 AI Agent は何でも良いのですが、最近勢力を上げてきているOpenAIの Codex を使ってみましょう。 VSCode とも統合されていて使いやすいですからね。 ハンズオン形式で記事を書くのでよかったら手元でも一緒に試してみてください。 ただし有料サービスを使うので、費用が発生する点にはご注意ください。 本日構築するイメージはこんな感じ。 同時接続数やパフォーマンスは気にしなくて良いのでDWHはスキップして良いでしょう。フルサーバレスでいきます。 本日の流れはこんな感じ。 AIに全体構成を考えてもらう AIに必要な権限を聞いて json で吐いてもらう 人が権限の内容を確認し、問題なければIAMを発行して権限を付与する 人が AWS CLI でIAMを設定し、AIにユーザーを伝える AIが分析用のダミーデータ一式を生成するための スクリプト を作成(物流系のデータ) AIが スクリプト からダミーデータを自動生成 AIがローカルのデータをS3に同期 AIがGlue CrawlerでS3のデータからカタログを作成 AIがクエリを考案してAthenaでクエリを投げて結果をS3に保存 人が Amazon Quick SuiteでAthenaをデータソースに設定 AIが分析用のプロンプトを考える AIがグラフを自動生成する(可視化) 人がグラフを評価し、AIにより深い指示を出してデータ分析を進める "AIが" で始まるタスクが圧倒的に多いですね。 人は初期設定やAIのアウトプットの評価に集中することができます。 これぞAI駆動 。 ここからが本題 1. AIに全体構成を考えてもらう まずはChatGPT5.1 Thinking モデルに全体構成を考えてもらいます。 ChatGPT5.1 Thinkingの考案した全体構成をmermaidで可視化してみると以下のような構成でした。 ローカルにデータを吐いて、S3に上げたのちに分析用にデータを分解。 BIは Amazon Quick Suiteを採用してビジネスユーザーが外からデータ閲覧する構成になっています。 イメージ通りの構成ですね。 2. AIに必要な権限を聞いて json で吐いてもらう ここからはCodexで作業します。もうThinkingモードは使いません。 必要な権限を json で吐き出してもらいます。 3. 人が権限の内容を確認し、問題なければIAMを発行して権限を付与する いくつかの権限に分けて出力してくれました。 各ポリシーを作成し、Codex用のIAMに付与します。(不要な権限が含まれていないか目視で確認しましょう) ハマりポイントとしてはGlue Crawlerを実行するためにGlueのサービスロールをパスロールで渡すところでしょうか。 それ以外は一般的なS3、Athena、Glueに対する操作権限です。 ※ バケット 作成後はリソースを絞りましょう 4. 人が AWS CLI でIAMを設定し、AIにユーザーを伝える IAMを作成したらシークレットキーを発行します。 黄色枠にあるように AWS CLI の aws configureでcodexというプロファイル名にシークレットキーを埋め込みます。 これで VSCode (Codex)が自律的に AWS リソースにアクセスして自由にデータを操作できるようになります。 5. AIが分析用のダミーデータ一式を生成するための スクリプト を作成(物流系のデータ) 今回は手元に何もデータがないのでダミーデータから考えねばなりません。 Codexに以下のタスクを依頼してみます。 以下のようなダミーデータ作成用の スクリプト が完成しました。 この スクリプト で生成されるダミーデータは以下の関係性になっています。 dim_:顧客や商品などの実データ fact_:配送や返品などの明細データ(実績データ) 6. AIが スクリプト からダミーデータを自動生成 先ほどの スクリプト でローカルにデータを作成してもらいます。 ローカル環境に以下のようにデータが作成されました。 7. AIがローカルのデータをS3に同期 ローカルに作成したファイルをS3に同期してもらいます。 プロンプトが見切れていますが、ローカルに作成したデータをS3にアップロードすることを指示しました。 AWS コンソールでS3を開き、データが無事に同期されていることを確認します。 8. AIがGlue CrawlerでS3のデータからカタログを作成 GlueのCrawlerの権限はパスロールで渡してあるので、以下の一連の作業をすべて委譲することができます。 S3のデータをスキャンして スキーマ を自動推論し、Data Catalogにテーブルを作成します。 9. AIがクエリを考案してAthenaでクエリを投げて結果をS3に保存 注文件数、配送遅延を分析し、その結果を CSV 形式でS3 バケット に保存します。 S3 バケット のathena-results/に結果が格納されていることを確認。 チャットの方にも5月の配送遅延の平均は122分と記載されていますが、この数値はAthenaで実際にクエリを実行した結果と一致しています。 (ダミーデータの品質がイケてないため、平均遅延2時間という狂った世界になっていますがいったん無視してください。5月に何があったんだ・・・。) ついでに分析用にビューをいくつか作成しておきます。Codex経由で作成しました。 10. 人が Amazon Quick SuiteでAthenaをデータソースに設定 ここから少しだけ人の手による作業が必要です。 AWS コンソールで Amazon Quick Suiteを開きます。 (※ Amazon Quick Sightの開始画面が Amazon Quick Suiteに切り替わっています) ちなみに Amazon Quick Suiteのフル機能は以下のリージョンでのみ提供されており、東京はまだです。 引用: Amaozn Quick Suite 初期設定画面でリージョンを東京に切り替えます。 「アカウント作成」を押下すると以下の画面に遷移します。作成に1分ほどかかります。 アカウント作成が完了。 「 Amazon Quick Suiteに遷移」を押下することで ダッシュ ボードに遷移します。 デフォルトの ダッシュ ボードが開きました。 次は権限の設定を行います。 「Quick Suiteを管理」を押下します。 「 AWS リソース」を選択します。 S3とAthenaを有効にします。 S3は バケット も選択しておきます。 次はSPICE容量を購入します。 SPICE (Super-fast, Parallel, In-memory Calculation Engine) の頭文字から来ています。かっこよすぎる。 いったん1GBを購入。 参考: SPICEの価格 容量が追加されていることを確認。 次はデータソースの接続です。 Athenaを選択して次へ。 データソース名を設定し、「接続を検証」を押下。 検証済みになればOK。 追加したデー タセット を押下。 「デー タセット の作成」を押下。 先ほど作成したビュー(v_fact_shipments)をSPICEに追加します。 迅速な分析のためにSPICEにインポートを選択して「視覚化する」を押下。 取り込みが完了すると以下の画面に遷移します。 この画面でデータの可視化を行います。 棒グラフを手動で追加してみる。 X軸にcarrier_id(配送業者)を追加すると配送業者別のレコード数の棒グラフが表示された。 11. AIが分析用のプロンプトを考える 先ほど作成したビューでどんな可視化が有効か、いくつかパターンを考えてもらいます。 日時の出荷件数の推移を可視化しましょう。 12. AIがグラフを自動生成する(可視化) ビジュアルタブで「構築」を押下する。 画面の右にAmazonQが表示される。 プロンプト①:日別の出荷件数を折れ線グラフで作成してください。期間は2024-05の1か月とします。 ものの数秒でグラフが作成されました。 「ADD TO ANALYSIS」を押下して ダッシュ ボードにグラフを追加してみます。 日別の出荷件数の折れ線グラフ を ダッシュ ボードに追加できました。 プロンプト②:配送業者ごとの平均の遅延(分)を棒グラフで。実績がない行は除外。値は delay_min の平均。大きい順に並べて、数値ラベルも表示して。 とやりたいところですが、「遅延」は計算が必要なので、まずは計算フィールドを作成する必要があります。 計算フィールドも 自然言語 で作成可能です。「+計算フィールド」を押下します。 どんな値が欲しいのかを 言語化 して伝えます。 式が提案されるので、「挿入」を押下する。予定到着時刻と実績到着時刻の差を計算しています。 計算フィールドを設定して「保存」を押下。 計算フィールド(delay_min)が追加されました。 次はこれを使ってグラフを作成します。 先ほどのプロンプトを再実行すると 配送業者別の平均遅延時間のグラフ を追加することができました。 13. 人がグラフを評価し、AIにより深い指示を出してデータ分析を進める では先ほどの配送のビューからより深堀分析を進めます。 いくつかのビューを提案してくれたので作成を依頼します。 権限は渡してあるので作成を進めてくれます。 Glueを確認すると追加で複数のビューが作成されていることを確認できました。 新規ビューのv_shipments_enrichedを選択して、SPICEにインポートする。 先ほどの画面で「デー タセット を追加」を押下する。 SPICEに取り込み済みのv_shipments_enrichedを選択する。 v_shipments_enrichedの可視化方針を決めます。 先ほどと同様に計算フィールドをAmazonQを使って 自然言語 で追加します。 プロンプト③:月別×配送業者のオンタイム率をヒートマップで表示 月別の配送業者別のオンタイム率(予定通りに配送できた割合)をヒートマップで表示することができました。 さいごに 記事が長くなってしまうのでこの辺で終わります。 今回のポイントとしては以下の2点です。 Codex × AWS CLI でデータ分析基盤を自動構築できる AmazonQ in Quick Suiteなら非エンジニアでも簡単にデータの可視化が行える 扱うのに少しだけ AWS の知識が必要ですが、AIの力でデータ分析の 民主化 がまた一歩進んだ感じがあります。 これからも AWS やAI関連の検証記事をたくさん書いていきます。 ↓ のスターを押していただけると嬉しいです。励みになります。 最後まで読んでいただき、ありがとうございました。 エンタープライズ 第一本部では一緒に働いてくださる仲間を募集中です。以下のリンクからお願いします。 私たちは一緒に働いてくれる仲間を募集しています! 中途採用-エンタープライズ第一本部 新卒採用-エンタープライズ第一本部 執筆: 英 良治 (@hanabusa.ryoji) レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
はじめまして。XI本部 クラウド イノベーション センター所属、2年目の米田です。 AWS re:Invent 2024 にて発表され、2025年5月に正式リリースされた「 Amazon Aurora DSQL(以下Aurora DSQL)」について触れる機会がありましたので、こちらで共有いたします 。 今回の記事では特に、 Aurora DSQLの仕様や、Terraformを活用したIaC(Infrastructure as Code)化 の観点から、その特徴や実践ポイントをわかりやすく紹介します。 これから Aurora DSQL の導入を検討されている方にとって、少しでも参考になる内容になれば幸いです。 Aurora DSQLとは データベースへの認証 データベースロール トークン認証 IaC化してみる 実行結果 注意すべきポイント まとめ 参考文献 Aurora DSQLとは Aurora DSQLは、従来のAuroraと比較して、より高可用性なフルマネージドのサーバーレス分散 SQL データベースになります。ここで、フルマネージドサービスとは、 ユーザーがインフラ運用を意識せず、 AWS が裏で全部管理・自動化してくれる サービスのことを指します。具体的には、Aurora DSQLは クラスタ ー管理や容量計画が不要で、データは自動的に複数AZへ分散されるため、運用負荷の大幅な軽減が期待されます。 AWS 公式ドキュメントでも以下のように記載されており、Aurora DSQLが高い可用性とほぼ無制限のスケーラビリティを実現する次世代データベースサービスであることがわかります。 Amazon Aurora DSQL は、 トランザクション ワークロード用に最適化されたサーバーレスの分散リレーショナルデータベースサービスです。Aurora DSQL は実質的に無制限のスケールを提供し、インフラスト ラク チャを管理する必要はありません。アクティブ/アクティブ高可用性 アーキテクチャ は、 99.99 % の単一リージョンと 99.999% のマルチリージョンの可用性を提供します。 ( AWS 公式ドキュメントより引用) 今回触れるきっかけとなったのは、以下3点にメリットを感じてになります。 1,分散データ処理 単一リージョンで 99.99 %、マルチリージョンで99.999%の可用性を提供するアクティブ/アクティブ構成の高可用性を実現してくれます。 2,インフラスト ラク チャの管理が完全に不要 フルマネージドのサーバーレスサービスであるため、パッチ適用/アップグレード/メンテナンス作業が発生せず、バックアップに関しても AWS Backupを利用することで自動化できます。 3,使用量に応じた従量課金制 Aurora DSQLにかかるコストは従量課金制なため、従来のデータベースと比較してコスト効率よく運用することができます。例えば、アクセス頻度がそれほど高くない ユースケース では、常時稼働する インスタンス 単位(時間課金)のデータベースと比べて、Aurora DSQL のような使用量に応じた課金モデルの方が、よりコスト効率よく利用できる可能性があります。 具体的にはDPUと呼ばれるデータベースの処理能力単位に比例して課金されるため、実際に利用する際にはCloudwatchやAurora DSQLの ダッシュ ボードから以下のようなDPU消費メトリクスを確認するのが良いでしょう。 WriteDPU:Aurora DSQL クラスタ ーの書き込み時に使用したDPU ReadDPU:Aurora DSQL クラスタ ーの読み込み時に使用したDPU ComputeDPU:Aurora DSQL クラスタ ーの計算時に使用したDPU データベースへの認証 Aurora DSQLへアクセスするにあたって、誰でもアクセスできてしまってはセキュリティ上好ましくありません。そこで重要になってくるのが データベースロール と トーク ン認証 です。 データベースロール データベースロールとは、その名のとおり実際にデータベースを操作するために必要なロールで、これはIAMロールとはまた別物である点に注意してください。 データベースロールは、全てのアクションが可能な Adminロール と、ユーザが定義した範囲内でアクションが可能な カスタムロール の、2つに分けられます。とりあえずAdminロールを使用してアクセスすれば全てのテーブル操作が可能ですが、 AWS のベストプ ラク ティスに則ってセキュリティ面を考慮すると、必要最低限のアクションを定義したカスタムロールを使用するべきです。 以下の表に、データベースロールごとの主な違いをまとめました。 データベースロール 権限範囲 主な操作 推奨用途 Adminロール 全権限 CREATE、 DROP 、SELECT、INSERT、UPDATE、DELETE等全て 管理者や全てのテーブルにアクセスする必要のあるリソースに対して利用 カスタムロール ユーザー定義 必要な操作のみ許可 Aurora DSQL操作を必要最低限に抑えたい場合に利用 カスタムロールはDSQL クラスタ 内部で作成します。作成した後はIAMロールと紐づけることで、紐づけ先IAMロールにDB操作権限を紐づけることができます。 ※Adminロールはデフォルトで存在しており、作成したり紐づけたりといった作業は不要です トーク ン認証 IAMロールをデータベースロールに紐づけたので、そのIAMロールを用いてAurora DSQLにアクセスしてみよう、と思うかもしれませんが、それではうまくいきません。 実際はAurora DSQLの認証には トーク ン が必要になります。ややこしいかもしれませんが、IAMロールを用いて可能なのはあくまでもAurora DSQLの トーク ンの取得であり、実際にデータベースへアクセスするにはその トーク ンを使用した認証が必要になります。 トーク ンを取得する方法は2通りあり、一つはAdmin権限として取得する方法(Adminロール)、もう一つはカスタムロールで定義した権限内の操作のみを許可する トーク ンを取得する方法(カスタムロール)です。後者の場合の私なりのイメージを図にしました。 Admin権限 トーク ンをリク エス トする場合と、紐づけたカスタムロールで定義した権限 トーク ンをリク エス トする場合とで、エンドポイントが異なります(以下 CLI での実行例) # Adminの場合 aws dsql generate-db-connect-admin-auth-token \ --hostname <DSQL Endpoint> \ --region ap-northeast- 1 \ --expires-in 3600 #トークン期限 # カスタムロールの権限内に限定したトークン取得の場合 aws dsql generate-db-connect-auth-token \ --hostname <DSQL Endpoint> \ --region ap-northeast- 1 \ --expires-in 3600 同様に、IAMロールにアタッチしておくべきアクションもデータベースロールによって異なります。Adminとして トーク ンを取得する場合は dsql:DbConnectAdmin アクションが必要となる一方で、IAMロールに紐づけたカスタムロールに権限を絞る場合は dsql:DbConnect アクションが必要となります。 取得した トーク ンを用いてAurora DSQLに接続し、クエリを実行することで必要な操作を実現します。 このように、IAM認証と トーク ン認証を組み合わせることで、セキュアにAurora DSQLへアクセスすることが可能になります。 IaC化してみる ここまでの流れを実際にIaC化してみようと思います。実装にはTerraformを利用し、プロバイダーはTerraform公式の AWS Providerを用いることにします。 今回はAurora DSQL クラスタ と、データ取得・更新を実施するLambda関数というめちゃくちゃ簡単な アーキテクチャ をTerraformで用意してみました。 今回利用する環境は以下のとおりです provider:hashicorp/ aws version:v6.14.1 まず、 クラスタ の作成にはTerraformの AWS Providerでサポートされている aws _dsql_cluster リソースを利用します。 resource "aws_dsql_cluster" "this" { deletion_protection_enabled = true tags = { Name = "BlogCluster" } } シングルリージョン 削除保護の有効化 タグ設定:TestCluster 次にLambdaにアタッチする実行ロールを作成します。(Lambda関数自体の作成は割愛し、ここではIAMロールの作成だけを記載してます) data "aws_iam_policy_document" "dsql_role_assume_role" { statement { actions = [ "sts:AssumeRole" ] principals { type = "Service" identifiers = [ "lambda.amazonaws.com" ] } } } data "aws_iam_policy_document" "dsql_role" { statement { effect = "Allow" actions = [ "dsql:DbConnect" ] resources = [ "<DSQL Endpoint>" ] } } resource "aws_iam_role" "dsql_role" { name = "dsql-blog-role" assume_role_policy = data.aws_iam_policy_document.dsql_role_assume_role.json } resource "aws_iam_role_policy_attachment" "dsql_role" { role = aws_iam_role.dsql_role.name policy_arn = aws_iam_policy.dsql_role.arn } resource "aws_iam_policy" "dsql_role" { name = "dsql-blog-policy" policy = data.aws_iam_policy_document.dsql_role.json } 最後にAurora DSQLでカスタムロールの作成とIAMロールへの紐づけを実施します。 マッピング 対象のIAMロールは、今回はLambda関数の実行ロールになります。Aurora DSQLへの接続は AWS コンソールより可能で、Cloudshell上でクエリを実行できます。 以下はblog_userというカスタムロールを作成する流れです。 # blog_userというカスタムロールを作成 CREATE ROLE 'blog_user' WITH LOGIN; # カスタムロールとIAMロールのマッピング AWS IAM GRANT 'blog_user' TO '<Lambda IAM Role ARN>' ; # カスタムロールにprivateスキーマへのアクセス権限を付与 GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA private TO 'blog_user' ; ただ、コンソールからの手動作成ではなく、実際はIaCの実行と同時に自動でクエリも実行されることが望ましいと思います。それにより、運用の負担を軽減し、ヒューマンエラーの低減が期待できます。 そこで今回は、Terraformの実行と組み合わせて、自動でカスタムロール作成まで実施してくれるように実装してみました。具体的にはTerraform実行時に外部 スクリプト を呼び出し、リソースの構築と同時に スクリプト で定義したクエリを実行する、といったものです。 以下実装コードになります。 resource "terraform_data" "this" { triggers_replace = { cluster_id = aws_dsql_cluster.this.identifier file_sha256 = filebase64sha256 ( "$ { path.module } /scripts/dsql.sh" ) } #shell scriptを呼び出して実行 provisioner "local-exec" { command = "sh $ { path.module } /scripts/dsql.sh" } } #!/bin/bash # トークン生成 export PGPASSWORD=$(aws dsql generate-db-connect-admin-auth-token \ --expires-in 3600 \ --region ap-northeast- 1 \ --hostname <DSQL Endpoint>) export PGSSLMODE=require # psql でロール作成 psql --dbname postgres --username admin --host <DSQL Endpoint> <<EOF CREATE ROLE blog_user WITH LOGIN; AWS IAM GRANT blog_user TO '<IAM Role ARN>' ; CREATE SCHEMA IF NOT EXISTS private; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA private TO blog_user; \q EOF あとはterraform init→terraform plan→terraform applyの順でコマンドを実行すれば、DSQL クラスタ ーが自動的にプロビジョニングされ、データベースロールの作成とIAMロールへの紐づけ、アクセス権限の付与までの処理が自動で走ります。 これにより手動での操作が軽減され、より効率的にリソースが作成されるようになりました。 実行結果 Lambdaを呼び出して、Aurora DSQLへの接続と基本的なクエリ実行が正常に動作することを確認します。 クリックして展開 import boto3 import ssl import pg8000 def lambda_handler (event, context): # Aurora DSQL 情報 dsql_endpoint = "<DSQL Endpoint>" aws_region = "ap-northeast-1" # トークン生成 dsql_client = boto3.client( 'dsql' , region_name=aws_region) auth_token = dsql_client.generate_db_connect_auth_token( Hostname=dsql_endpoint, ExpiresIn= 900 ) # SSL 設定 ssl_context = ssl.create_default_context() ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_REQUIRED # データベース接続 conn = pg8000.connect( host=dsql_endpoint, database= 'postgres' , user= 'blog_user' , password=auth_token, port= 5432 , ssl_context=ssl_context ) # blogテーブルの内容を取得 cursor = conn.cursor() cursor.execute( """ SELECT member_id, title, content FROM private.blog; """ ) columns = [desc[ 0 ] for desc in cursor.description] rows = cursor.fetchall() blogs = [ dict ( zip (columns, row)) for row in rows] cursor.close() conn.close() return { "blogs" : blogs } 以上を実行すると、事前にprivate スキーマ に作成したblogテーブルからデータを取得することができました。 { "blogs" : [ { "member_id" : 1 , "title" : "はじめての投稿" , "content" : "これは最初のブログ記事です。" } , { "member_id" : 2 , "title" : "お知らせ" , "content" : "システムをアップデートしました。" } , { "member_id" : 3 , "title" : "Tips" , "content" : "PostgreSQLでスキーマを使い分けよう。" } ] } 注意すべきポイント 最後に、実際に触ってみてDSQLならではの注意すべき点と感じたポイントをまとめてみました。 接続には トーク ン認証が必須になりますが、 デフォルトで15分、最大で1週間 という期限が設定されています。したがって、定期的に トーク ンの更新処理が発生します。今回はリク エス トの都度 トーク ンを取得してくる想定で実装しましたが、 トーク ンを再利用する場合は注意が必要です。 DSQLは PostgreSQL と互換性があり、大変便利なサービスですが、逆に言えば PostgreSQL 以外のデータベースシステムとは互換性がありません。ですので、 PostgreSQL で対応できない処理に関してはおすすめできません 。 スキーマ の作成は Adminロールでしか実施できません 。カスタムロールで試行しても、権限不足というエラーメッセージが出力されます。したがって、 スキーマ 作成者は誤った操作をしないように注意が必要です。 ただし、本記事でも紹介したようにデータベース操作ごと自動化してしまえば、人的ミスも未然に防ぐことができます。 DSQLでは、 ALTER TABLE で列の型変更や削除、ユニークキーの追加ができません 。したがって、テーブルを編集した場合は、テーブルの削除→新規作成という手順が必要になります。新規作成になるのでテーブル単位の権限を与えていた場合は再度権限を与える必要があります。このことを考えると、カスタムロールに与える権限は スキーマ 単位で与えておくのが良いと思います。 GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA <スキーマ> TO <カスタムロール>; 部分インデックスがサポートされていないので例えば以下のように特定の条件に一致する行のみにインデックスを作成することができません。(これはなにかと不便ですね。。。) CREATE INDEX idx_user_id ON setting_category (category, user_id) WHERE category = '重要' ; 1つの トランザクション で変更可能なテーブル行の最大値が3000行となっているようで、それ以上の処理を実施する際にはページングなどが必要になります。 まとめ 本記事では比較的新しいサービスであるAurora DSQLを触ってみた感想をお伝えしました。 PostgreSQL 互換ということもあって非常に使いやすく、コストも他のデータベースサービスと比べて比較的安価に利用できるサービスだと思います。 今回はIaC化による構築手順についてメインでお伝えしましたが、今後は運用した中で得られた知見やベストプ ラク ティスについても共有していきたいと思います。 もし今後Aurora DSQLの導入を検討されている方に参考になればと思います。 参考文献 https://qiita.com/har1101/items/7dd1a6d803e48e3e0525 https://docs.aws.amazon.com/ja_jp/aurora-dsql/latest/userguide/what-is-aurora-dsql.html https://docs.aws.amazon.com/ja_jp/aurora-dsql/latest/userguide/SECTION_authentication-token.html https://docs.aws.amazon.com/ja_jp/aurora-dsql/latest/userguide/using-database-and-iam-roles.html 執筆: @yoneda.kosuke レビュー: @kobayashi.hinami ( Shodo で執筆されました )
こんにちは、クロス イノベーション 本部 AI トランスフォーメーションセンター所属の山田です。 今回はアプリケーションの認可(Authorization)に関して、Casbin というライブラリを使った設計・実装で紹介したいと思います。 認可(Authorization)と認証(Authentication)について 認可の話をするうえで前提として認可(Authorization)と認証(Authentication)の違いについて再確認しておきます。 認可(Authorization)は認証(Authentication)と混同されがちですが、区別して考えるのが重要です。 アプリケーションの認証は「ユーザーが誰であるか」を確認する役割を果たします。 一方で、認可は「ユーザーが何ができるか」を判断します。 最近のアプリケーションにおいて認証は、IdP(Identity Provider)に委譲するケースが多く、アプリケーションは IdP から発行された トーク ンを検証することで「ユーザーが誰であるか」の認証部分を実装できるようになりました。 一方で認可は依然としてアプリケーション固有のロジックです。 理由は単純で、アプリケーションの中で各ユーザーがどんな操作ができるかはサービスごとに異なるからです。 これらはアプリケーションごとに設計し、実装する必要があります。 認可ロジックの基本要素 実際にシステムで認可を扱う際に最も基礎となる要素を分解すると「ユーザー」、「リソース」、「操作」の 3 つになります。 要素 意味 ユーザー 操作を試みる主体 リソース 操作対象のオブジェクト 操作 リソースに対して行いたい操作 認可における「ユーザー」「リソース」「操作」の関係 具体例にこの 3 つの要素を当てはめて、認可というものが何かを考えてみましょう。 「アリス(ユーザー)が文書 1(リソース)を閲覧(操作)できるか?」 認可とは、この問いに対して 「Yes」か「No」 かを判断する仕組みです。 つまり、認可は「誰が(ユーザー)」「何に対して(リソース)」「何をするか(操作)」の三要素の組み合わせで構成されるルールセットといえます。 このように要素を分解して整理することで、アプリケーションにおいて認可の設計を具体的な ドメイン 設計の一部として考えることができるようになります。 アプリケーション固有のルールも、最終的にはこの三要素の組み合わせとして表現できます。 Casbin とは Casbin はここまで紹介してきた認可の処理をアプリケーションに実装する際に利用できるライブラリです。 Casbin は非常に柔軟なライブラリで大体の認可ロジックを実装できます。 オリジナルの実装は Go 言語ですが、 Java 、 Python 、 JavaScript など他の プログラミング言語 の実装もあります。 casbin.org Casbin の概念 Casbin 利用時は 「Model」、「Policy」、「Enforcer」 という 3 つの要素からなります。 これらの 3 要素の意味はそれぞれ以下です。 要素 意味 Model アクセス制御のモデルを定義するもの Policy 権限データの実体 Enforcer Model と Policy を読み込み、要求に対し認可ロジックを実行するエンジン Casbinにおける「Model」、「Policy」、「Enforcer」の関係 Model はアクセス制御の論理的な骨組みを定義します。 アプリケーションに組み込む場合、Model は静的であり、 model.conf のようなファイルベースで管理されます。 Policy は Model 側で決められた構造に従った実際の権限データの実態です。 こちらは Model とは対照的に動的であることがほとんどです。 Policy も policy.csv のようにファイルベースで管理できますが、データベースでの管理もできます。 Casbin でのアクセス制御モデル Casbin では「Request」、「Policy」、「Matcher」、「Effect」 という 4 つの概念を使ってアクセス制御モデルを構成します。 要素 意味 Request Request は認可判断の入力で、操作を行う主体(sub)、 アクセス先のオブジェクト(obj)、実施する操作(act)が含まれます。 Policy Policy は権限データの構造を定義します。基本的には p={sub, obj, act} ですが、ポリシーのマッチング結果の eft 列を明示的に追加し、 p={sub, obj, act, eft} にもできます。 Matcher Matcher は Request と Policy のマッチングルールを定義します。 Effect Effect は最終的な認可の決定ルールを定義します。 実際のアクセス制御モデルの構成を見ていきましょう。 最も基本のアクセス制御モデルである ACL ( Access Control List:アクセス制御リスト)は以下のようになります。 # Request definition [request_definition] r = sub, obj, act # Policy definition [policy_definition] p = sub, obj, act # Matchers [matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.act # Policy effect [policy_effect] e = some(where (p.eft == allow)) ポリシーに以下の権限データが格納されているとします。 p, alice, data1, read p, bob, data2, write この場合、Casbin では以下のように判断できます。 alice は data1 を read できます。 bob は data2 を write できます。 ここまでが Casbin のアクセス制御モデルの基本になります。 Casbin を実際に使ってみる Casbin の概念についてはある程度触れたので実際に Casbin を使ってみましょう。 今回は Casbin の Python 実装である PyCasbin を利用します。 github.com pip install pycasbin model.conf と policy.csv を用意し、それぞれをロードします。 import casbin # Enforcerの初期化 e = casbin.Enforcer(model= "./model.conf" , adapter= "./policy.csv" ) enforcer メソッドを使って権限のチェックを実施します。 # 権限のチェック # 例: "alice"が"data1"に対して"read"権限を持っているか確認 result_1 = e.enforce( "alice" , "data1" , "read" ) assert result_1 == True # 例: "bob"が"data2"に対して"write"権限を持っているか確認 result_2 = e.enforce( "bob" , "data2" , "write" ) assert result_2 == True # 例: "alice"が"data2"に対して"write"権限を持っているか確認 result_3 = e.enforce( "alice" , "data2" , "write" ) assert result_3 == False このようにして、Casbin ではアクセス制御モデルのルールをもとに権限チェックを行えます。 権限データ(Policy)をデータベースから読み取る 次に、より実践的なパターンとして権限データをデータベースから読み取る例を紹介します。 今回は RDB の PostgreSQL を使って権限データを管理します。 説明を省いていましたが、Casbin では Policy の読み取りは、Adapter というモジュールを介して行われます。 CasbinでのAdapterモジュールの位置付け PyCasbin で権限データの管理に RDB を扱う場合には、SQLAlchemyベースでDBとやり取りをする casbin_sqlalchemy_adapter という Adapter モジュールの利用がおすすめです。 github.com 今回は上記の casbin_sqlalchemy_adapter と PostgreSQL への接続に使うために psycopg2 の2つのライブラリを依存関係に追加します。 pip install psycopg2 pip install casbin_sqlalchemy_adapter PostgreSQL を使うために、Adapter で PostgreSQL への接続情報を与えます。 import casbin_sqlalchemy_adapter DB_USER_NAME = "<データベースのユーザー名>" DB_PASSWORD = "<データベースのパスワード>" DB_HOST = "<ホスト名 or IPアドレス>" DB_NAME = "<データベース名>" adapter = casbin_sqlalchemy_adapter.Adapter(f "postgresql+psycopg2://{DB_USER_NAME}:{DB_PASSWORD}@{DB_HOST}:5432/{DB_NAME}" ) e = casbin.Enforcer( "./model.conf" , adapter=adapter) casbin_sqlalchemy_adapter では、上記のように設定すると PostgreSQL のデータベース上に casbin_rule テーブルが自動で作成されます。 作成される casbin_rule テーブルは以下のような構造になります。 Column | Type | Collation | Nullable | Default --------+------------------------+-----------+----------+----------------------------------------- id | integer | | not null | nextval('casbin_rule_id_seq'::regclass) ptype | character varying(255) | | | v0 | character varying(255) | | | v1 | character varying(255) | | | v2 | character varying(255) | | | v3 | character varying(255) | | | v4 | character varying(255) | | | v5 | character varying(255) | | | Indexes: "casbin_rule_pkey" PRIMARY KEY, btree (id) Casbin ではポリシーデータを書き込むメソッドも用意されているため、こちらを利用して、動的に権限データをデータベースに永続化できます。 # 権限データの書き込み added = e.add_policy( "alice" , "data1" , "read" ) # 新規行追加の場合はTrueが返る assert added == True # 権限のチェック result = e.enforce( "alice" , "data1" , "read" ) assert result == True より実践的な使い方(1):RBAC ここまでで ACL ベースのアクセス制御を見てきましたが、実際のアプリケーションではロールベースのアクセス制御である RBAC が用いられることが多いので、RBAC のパターンも紹介します。 RBAC では model.conf に以下のように role_definition を追加し、 matchers でロールまで見るように設定します。 [request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act # ロールの継承関係の定義 [role_definition] g = _, _ [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act [policy_effect] e = some(where (p.eft == allow)) RBAC ではロールに対し、操作権限を付与する形になります。 今回は editor と reader という 2 つのロールで考えてみましょう。 editor は data に対し read 、 write が可能、 reader は data に対し、 read だけが可能とします。 一度、権限データはすべて削除した状態とします。 # ロール定義を追加 e.add_policies( [ [ "editor" , "data" , "write" ], [ "editor" , "data" , "read" ], [ "reader" , "data" , "read" ], ] ) この状態で alice には editor ロール、 bob には reader ロールを付与します。 # ユーザーにロールを割り当て e.add_role_for_user( "alice" , "editor" ) e.add_role_for_user( "bob" , "reader" ) ここまででテーブルは以下の状態になります。 id ptype v0 v1 v2 v3 v4 v5 1 p editor data write NULL NULL NULL 2 p editor data read NULL NULL NULL 3 p reader data read NULL NULL NULL 4 g alice editor NULL NULL NULL NULL 5 g bob reader NULL NULL NULL NULL この状態で権限チェックを行います。 # 権限のチェック result_1 = e.enforce( "alice" , "data" , "write" ) assert result_1 == True result_2 = e.enforce( "alice" , "data" , "read" ) assert result_2 == True result_3 = e.enforce( "bob" , "data" , "read" ) assert result_3 == True result_4 = e.enforce( "bob" , "data" , "write" ) assert result_4 == False RBAC の重要なポイントはロールに対しリソースへの操作権限が割り当てられていることです。 RBACのイメージ図 アプリケーションの認可ロジックにおいて RBAC を採用することで、権限制御がユーザーに対して割り当てるロールの変更を行うだけで実現でき管理コストの軽減が見込めます。 ロールの継承について ここまでで紹介した editor のロールは redaer の上位ロールと考えることができます。 これを図で表現すると以下のようになります。 RBACにおけるロールの継承のイメージ図 Casbin では、このようなロールの継承も表現できます。 具体的に見ていきましょう。 editor ロールで data に関する read 操作の権限情報を消し、 editor ロールが reader ロールを継承するように設定をします。 # Editorロールからread権限を削除 e.delete_permission_for_user( "editor" , "data" , "read" ) # editorロールをreaderロールの上位に設定 e.add_role_for_user( "editor" , "reader" ) ここまででテーブルは以下の状態になります。 id ptype v0 v1 v2 v3 v4 v5 1 p editor data write NULL NULL NULL 3 p reader data read NULL NULL NULL 4 g alice editor NULL NULL NULL NULL 5 g bob reader NULL NULL NULL NULL 6 g editor reader NULL NULL NULL NULL この状態で alice が data を read できるかを問い合わせると、 True が返ることが確認できます。 # 権限のチェック result = e.enforce( "alice" , "data" , "read" ) assert result == True ユーザーとロール定義の重複への対処 Casbin では権限の設定はすべて文字列ベースで行われるためロール、ユーザーの区別がありません。 これはすなわち editor や reader というユーザーがいる場合、ロール定義と重複してしまうため正しく権限チェックが行えなくなります。 そこで実際にアプリケーションで権限設定を行う場合は定義に プレフィックス をつけるなどで対応するのが良いでしょう。 例えばロール定義には role: 、ユーザーの定義には user: のような プレフィックス を付けます。 このようにすることで重複に対応できます。 # 権限情報の定義 e.add_policies( [ [ "role:editor" , "data" , "write" ], [ "role:reader" , "data" , "read" ], ] ) # ロール情報の定義 e.add_role_for_user( "role:editor" , "role:reader" ) e.add_role_for_user( "user:alice" , "role:editor" ) e.add_role_for_user( "user:bob" , "role:reader" ) グループ管理への応用 ロールの継承はユーザーを集合したグループへのロール定義に応用できます。 ユーザーはグループに属し、グループに対してロールを付与するというシナリオを考えてみましょう。 今回は alice はグループ red に所属していて、グループ red にロール editor が与えられているとします。 これを図で表現すると以下のようになります。 グループに対してロールを付与する場合のイメージ図 先に紹介したユーザーとロール定義の重複を考慮して、グループの情報には group: という プレフィックス を付けます。 # 権限情報の定義 e.add_policies( [ ["role:editor", "data", "write"], ["role:reader", "data", "read"], ] ) # ロール/グループ情報の定義 e.add_role_for_user("role:editor", "role:reader") e.add_role_for_user("group:red", "role:editor") e.add_role_for_user("user:alice", "group:red") ここまででテーブルは以下の状態になります。 id ptype v0 v1 v2 v3 v4 v5 1 p role:editor data write NULL NULL NULL 2 p role:reader data read NULL NULL NULL 3 g role:editor role:reader NULL NULL NULL NULL 4 g group:red role:editor NULL NULL NULL NULL 5 g user:alice group:red NULL NULL NULL NULL alice はグループ red に所属しているため、 editor 権限を持ち、 data の読み書き操作ができます。 # 権限のチェック result_1 = e.enforce( "user:alice" , "data" , "write" ) assert result_1 == True result_2 = e.enforce( "user:alice" , "data" , "read" ) assert result_2 == True その他に、ユーザーが持つロール、所属しているグループの一覧は get_roles_for_user メソッドで取得可能です。 # ユーザーが所属しているグループ・付与されているロールの一覧の取得 rolls = e.get_roles_for_user( "user:alice" ) assert rolls == [ "group:red" , "role:editor" ] なお上記結果からも分かる通り、Casbin はグループ・ロールを区別しないため、所属しているグループの情報やロールの情報を抽出したい場合は、 プレフィックス まで見る必要があります。 より実践的な使い方(2):権限データのフィルタリング ここまでのやり方では、権限データを読み取るためにテーブルをすべて読み取る必要がありました。 これは少量のデータでは問題になりませんが、運用を見据えてデータ量が増えていくとパフォーマンス面で問題を引き起こす可能性があります。 そこで Casbin では権限データをフィルタリングして必要な権限データだけを読み取る機能をサポートしています。 一部のアダプタ実装では、フィルタされた権限データのロードをサポートしていません。 model.conf を編集し、特定の関心事( ドメイン )での権限データであることを権限判定ロジックに加えます。 この時、データをフィルターしやすいように ptype が p 、 g のどちらの場合でも同じ列に ドメイン の情報が入るようにするのが良いです。 # リクエスト定義にドメインを追加 [request_definition] r = sub, obj, act, domain # 権限データの定義にドメインを追加 [policy_definition] p = sub, obj, domain, act # ロール定義にドメインを追加 [role_definition] g = _, _, _ # 判定ロジックでドメインの一致を追加 [matchers] m = g(r.sub, p.sub, r.domain) && r.domain == p.domain && r.obj == p.obj && r.act == p.act [policy_effect] e = some(where (p.eft == allow)) この状態で、それぞれの ドメイン ごとに権限データを登録します。 # 2つのドメインを定義 domain_1 = "alpha" domain_2 = "beta" # ドメインごとのロールと権限を追加 e.add_policies([ [ "role:editor" , "data" , domain_1, "write" ], [ "role:reader" , "data" , domain_1, "read" ], [ "role:editor" , "data" , domain_2, "write" ], [ "role:reader" , "data" , domain_2, "read" ], ]) # aliceにはdomain_1のeditorロールを、bobにはdomain_2のeditorロールを割り当て e.add_role_for_user_in_domain( "user:alice" , "role:editor" , domain_1) e.add_role_for_user_in_domain( "user:bob" , "role:editor" , domain_2) ここまででテーブルは以下の状態になります。 id ptype v0 v1 v2 v3 v4 v5 1 p role:editor data alpha write NULL NULL 2 p role:reader data alpha read NULL NULL 3 g user:alice role:editor alpha NULL NULL NULL 4 g user:bob role:editor beta NULL NULL NULL ドメイン alpha の権限の検証の際に ドメイン beta の情報は不要であるため、フィルタしてロードすることが考えられます。 この時に使うのが load_filtered_policy メソッドです。 アダプタ側で用意されている Filter クラスを使って、条件を設定し、ロードする権限データを絞り込むことができます。 from casbin_sqlalchemy_adapter.adapter import Filter domain_1 = "alpha" domain_2 = "beta" f = Filter() f.v2 = [domain_1] # domain_1のポリシーのみをロード e.load_filtered_policy( filter =f) # 権限チェック result_1 = e.enforce( "user:alice" , "data" , "write" , domain_1) assert result_1 == True # domain_2のポリシーはロードされていないため、bobには権限がない状態になる bob_roles = e.get_roles_for_user_in_domain( "user:bob" , domain_2) assert bob_roles == [] まとめ 本記事では、アプリケーションの認可を組み込む際に利用できる Casbin というライブラリを紹介しました。 Casbin を利用することで、ロールの継承やグループへの権限の割当なども比較的にシンプルに実装できます。 アプリケーションの認可機能を実装する際には、候補として検討してみてはいかがでしょうか。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @yamada.y レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
はじめに こんにちは、クロス イノベーション 本部エンジニアリングテクノロ ジー センター、新卒1年目の大岡叡です。 今回は、TerraformでAuto Scaling Groupの挙動に悩んだ経験をきっかけに、 AWS Providerを参考にしながらCustom Providerを実装してみた話をご紹介します。実装を通じて、Terraform Providerの構造や拡張の仕組みを理解することができました。 はじめに Terraformとは Terraform Providerとは Custom Providerを実装したモチベーション Custom Providerの実装 目標と方針 ディレクトリ・ファイル構成 ソースコード全体 ビルドと設定 Custom Providerでリソースを作った結果 まとめ Terraformとは 私はTerraformを10月に部署配属されて初めて知ったので、知らない方のためにまずは簡単にTerraformについて説明します。 Terraformとは、HashiCorp社が提供する マルチ クラウド 対応のIaC(Infrastructure as Code)ツール です。 AWS 、Azure、 Google Cloudなど様々な クラウド リソースをコードで構築できます。 例えば、 AWS で東京リージョンに VPC を作る場合は以下のとおりです。 Terraform Providerとは Terraform公式サイト では、Providerについて以下のように説明しています。 Terraform relies on plugins called providers to interact with cloud providers, SaaS providers, and other APIs. Terraform configurations must declare which providers they require so that Terraform can install and use them. Additionally, some providers require configuration (like endpoint URLs or cloud regions) before they can be used. (Terraform公式サイトより引用) 要するに、Terraform ProviderとはTerraformと外部サービスをつなぐための プラグイン です。Terraformそのものはリソース定義や状態管理を行い、実際に クラウド と通信してリソースを作成・変更するのはProviderの役割です。 例えば、Terraformが AWS 上にリソースを構築する場合の大まかな流れは次のようになります。 ユーザーがTerraformコード(HCL)でリソースを定義 Terraformが対応するProviderをダウンロード Providerが AWS SDK (Go言語用の AWS 公式ライブラリ)を使って、 AWS API を呼び出す API のレスポンスを加工し、Terraformのステートファイル( terraform.tfstate )に反映する 主要な クラウド のTerraform Providerは、 GitHub で オープンソース として公開されており、実装の詳細を確認することができます。 Terraform AWS Provider: https://github.com/hashicorp/terraform-provider-aws Terraform Provider for Azure (Resource Manager): https://github.com/hashicorp/terraform-provider-azurerm Terraform Provider for Google Cloud Platform: https://github.com/hashicorp/terraform-provider-google また、Providerは自作することも可能であり、自作したProviderのことをCustom Providerと呼びます。 Custom Providerを実装したモチベーション Terraformを使い始めたばかりのある日、Auto Scaling Group(ASG)の構築に取り組んでいました。 まずは、以下のコードでASGを作成しました。 resource "aws_launch_template" "main" {   name_prefix = "main"   image_id    = "ami-01205c30badb279ec“   instance_type = " t2.micro " } resource " aws_autoscaling_group " " main " {   name                = " main "   max_size            = 3   desired_capacity    = 2   min_size            = 1   health_check_type   = " EC2 "   vpc_zone_identifier = [aws_subnet.main.id, aws_subnet.secondary.id]   launch_template {     id = aws_launch_template.main.id   } } 問題はここからです。 起動テンプレートにセキュリティグループ(SG)の設定を入れ忘れていたことに気づき、以下のように修正しました。 resource "aws_launch_template" "main" {   name_prefix   = "main"   image_id      = "ami-01205c30badb279ec"   instance_type = "t2.micro"   vpc_security_group_ids = [ var.ec2_security_group_id ] #SGを追加 } 起動テンプレートの設定を反映させるために再度 terraform apply を実行した後、マネジメントコンソールでASG管理下のEC2 インスタンス をすべて削除し、ASGに新しい インスタンス を起動させました。 しかし、起動した インスタンス を確認すると default のSGが設定されており、追加したSGが反映されていませんでした。 Terraform初心者だった私は、学習のために生成AIを使用しない縛りを自分に課していたこともあり、原因の特定に1時間ほどかかってしまいました。最終的に、原因は以下の AWS Providerの仕様だということが分かりました。 aws_autoscaling_group リソースの launch_template ブロックで version を指定しない場合、ASGは常に Default バージョンを参照する aws_launch_template リソースにおいて default_version や update_default_version といった引数で Default バージョンの設定をしない限り、最初に作成されたバージョン1が参照され続ける つまり、ASGに起動テンプレートの更新内容を反映させるには、 version = "$Latest" を明示的に指定する必要があったのです。 resource "aws_autoscaling_group" "main" { name = "main" ... launch_template { id = aws_launch_template.main.id version = "$Latest" # ← これを明示する必要があった! } } この仕様を理解したとき、「デフォルトで Latest を使ってくれたらいいのに……」と直感的に感じました。 一方で、Providerの仕様としては以下のような理由から妥当だとも思いました。 運用上、意図的に Default で固定したいケースがある AWS マネジメントコンソールでもASG作成時の初期値は Default(1) となっている そして、ちょうどその頃にProviderを自作できることを知ったこともあり、「 versionをデフォルトで $Latest にしてくれるProviderを自分で作って勉強してみよう 」と決意しました。 これが、今回のCustom Provider実装のモチベーションです。 Custom Providerの実装 目標と方針 今回作ったCustom Providerは「 awsmini 」という名前で、次のような方針で作りました。 aws_autoscaling_group と aws_launch_template のみをサポート aws_autoscaling_group の launch_template ブロックの version のデフォルト値を $Latest に設定 ローカルでビルドして、Terraformに直接読み込ませる方式を採用 実装は 公式AWS ProviderのGitHubリポジトリ を参考にしました。 ディレクト リ・ファイル構成 ディレクト リ・ファイル構成は以下のとおりです。 それぞれのファイルは、表1に示すように公式 リポジトリ を参考にして実装しました。 awsmini/ ├── bin/ # ビルド成果物 ├── internal/ │ ├── provider/provider.go # Provider定義 │ ├── conns/ │ │ ├── awsclient.go # AWSクライアント生成・キャッシュ │ │ └── config.go # AWS認証情報設定 │ └── service/ │ ├── autoscaling/group.go # ASGリソース実装 │ └── ec2/launch_template.go # Launch Templateリソース実装 └── main.go # エントリポイント 表1. awsmini内のファイルと公式 AWS Providerの対応関係 awsmini ファイル 公式 リポジトリ 内の参考ファイル main.go main.go internal/provider/provider.go internal/provider/sdkv2/provider.go internal/conns/awsclient.go internal/conns/awsclient.go internal/conns/config.go internal/conns/config.go internal/service/autoscaling/group.go internal/service/autoscaling/group.go internal/service/ec2/launch_template.go internal/service/ec2/ec2_launch_template.go ※注意:上記リンク先の ソースコード は、今後のProviderアップデートによりコード構造が変更される可能性があります。 そのため、執筆時点(2025年11月)での参考情報としてご覧ください。 また、 group.go 内で aws_autoscaling_group の launch_template ブロックの version 引数のデフォルト値を $Latest に設定しました。 "launch_template" : { Type: schema.TypeList, Optional: true , MaxItems: 1 , Elem: &schema.Resource{ Schema: map [ string ]*schema.Schema{ "id" : { Type: schema.TypeString, Optional: true , Computed: true , }, "name" : { Type: schema.TypeString, Optional: true , Computed: true , }, "version" : { Type: schema.TypeString, Optional: true , Default: "$Latest" , // ここでデフォルト値をLatestに設定 }, }, }, }, ソースコード 全体 group.go と launch_template.go のコードは分量が多いため、ここではそれ以外のファイルのみを掲載します。 前提として、 awsmini ディレクト リで以下のコマンドを実行しました(コマンド内の リポジトリ は実際に存在するものではありません)。 go mod init github.com/torut/terraform-provider-awsmini awsmini/main.go // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package main import ( "context" "flag" "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" "github.com/torut/terraform-provider-awsmini/internal/provider" ) func main() { var debugMode bool flag.BoolVar(&debugMode, "debug" , false , "set to true to run the provider with support for debuggers like delve" ) flag.Parse() opts := &plugin.ServeOpts{ ProviderFunc: func () *schema.Provider { ctx := context.Background() p, err := provider.New(ctx) if err != nil { log.Fatalf( "failed to create provider: %v" , err) } return p }, } if debugMode { ctx := context.Background() err := plugin.Debug(ctx, "local/awsmini" , opts) if err != nil { log.Fatalf( "failed to serve provider in debug mode: %v" , err) } return } plugin.Serve(opts) } awsmini/internal/provider/provider.go // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package provider import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/torut/terraform-provider-awsmini/internal/conns" "github.com/torut/terraform-provider-awsmini/internal/service/autoscaling" "github.com/torut/terraform-provider-awsmini/internal/service/ec2" ) // New returns a new, initialized Terraform Plugin SDK v2-style provider instance. // The provider instance is fully configured once the ConfigureContextFunc has been called. func New(ctx context.Context) (*schema.Provider, error ) { provider := &schema.Provider{ Schema: map [ string ]*schema.Schema{ "access_key" : { Type: schema.TypeString, Optional: true , Description: "The access key for API operations. You can retrieve this from the 'Security & Credentials' section of the AWS console." , }, "profile" : { Type: schema.TypeString, Optional: true , Description: "The profile for API operations. If not set, the default profile created with `aws configure` will be used." , }, "region" : { Type: schema.TypeString, Optional: true , Description: "The region where AWS operations will take place. Examples are us-east-1, us-west-2, etc." , }, "secret_key" : { Type: schema.TypeString, Optional: true , Description: "The secret key for API operations. You can retrieve this from the 'Security & Credentials' section of the AWS console." , }, "shared_config_files" : { Type: schema.TypeList, Optional: true , Description: "List of paths to shared config files. If not set, defaults to [~/.aws/config]." , Elem: &schema.Schema{Type: schema.TypeString}, }, "shared_credentials_files" : { Type: schema.TypeList, Optional: true , Description: "List of paths to shared credentials files. If not set, defaults to [~/.aws/credentials]." , Elem: &schema.Schema{Type: schema.TypeString}, }, "skip_credentials_validation" : { Type: schema.TypeBool, Optional: true , Description: "Skip the credentials validation via STS API. Used for AWS API implementations that do not have STS available/implemented." , }, "skip_region_validation" : { Type: schema.TypeBool, Optional: true , Description: "Skip static validation of region name. Used by users of alternative AWS-like APIs or users w/ access to regions that are not public (yet)." , }, "skip_requesting_account_id" : { Type: schema.TypeBool, Optional: true , Description: "Skip requesting the account ID. Used for AWS API implementations that do not have IAM/STS API and/or metadata API." , }, "token" : { Type: schema.TypeString, Optional: true , Description: "Session token for temporary credentials." , }, }, // Resources ResourcesMap: map [ string ]*schema.Resource{ "awsmini_autoscaling_group" : autoscaling.ResourceGroup(), "awsmini_launch_template" : ec2.ResourceLaunchTemplate(), }, DataSourcesMap: map [ string ]*schema.Resource{}, } provider.ConfigureContextFunc = func (ctx context.Context, d *schema.ResourceData) ( interface {}, diag.Diagnostics) { return configure(ctx, d, provider.TerraformVersion) } return provider, nil } // configure initializes the AWS client func configure(ctx context.Context, d *schema.ResourceData, terraformVersion string ) ( interface {}, diag.Diagnostics) { var diags diag.Diagnostics if terraformVersion == "" { terraformVersion = "0.11+compatible" } config := conns.Config{ AccessKey: d.Get( "access_key" ).( string ), Profile: d.Get( "profile" ).( string ), Region: d.Get( "region" ).( string ), SecretKey: d.Get( "secret_key" ).( string ), SkipCredsValidation: d.Get( "skip_credentials_validation" ).( bool ), SkipRegionValidation: d.Get( "skip_region_validation" ).( bool ), SkipRequestingAccountId: d.Get( "skip_requesting_account_id" ).( bool ), TerraformVersion: terraformVersion, Token: d.Get( "token" ).( string ), } // Handle shared config files if v, ok := d.GetOk( "shared_config_files" ); ok && len (v.([] interface {})) > 0 { config.SharedConfigFiles = expandStringList(v.([] interface {})) } // Handle shared credentials files if v, ok := d.GetOk( "shared_credentials_files" ); ok && len (v.([] interface {})) > 0 { config.SharedCredentialsFiles = expandStringList(v.([] interface {})) } // Initialize the AWS client client, err := config.ConfigureProvider(ctx) if err != nil { return nil , diag.FromErr(fmt.Errorf( "error configuring AWS provider: %w" , err)) } return client, diags } // expandStringList expands a list of interfaces to a list of strings func expandStringList(configured [] interface {}) [] string { vs := make ([] string , 0 , len (configured)) for _, v := range configured { val, ok := v.( string ) if ok && val != "" { vs = append (vs, val) } } return vs } awsmini/internal/conns/awsclient.go // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package conns import ( "context" "sync" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/autoscaling" "github.com/aws/aws-sdk-go-v2/service/ec2" ) // AWSClient holds the AWS SDK clients type AWSClient struct { awsConfig *aws.Config region string lock sync.Mutex // Service clients (cached) autoscalingClient *autoscaling.Client ec2Client *ec2.Client } // AwsConfig returns a copy of the AWS configuration func (c *AWSClient) AwsConfig(context.Context) aws.Config { return c.awsConfig.Copy() } // Region returns the configured AWS Region func (c *AWSClient) Region(context.Context) string { return c.region } // AutoScalingClient returns the Auto Scaling service client func (c *AWSClient) AutoScalingClient(ctx context.Context) *autoscaling.Client { c.lock.Lock() defer c.lock.Unlock() if c.autoscalingClient == nil { c.autoscalingClient = autoscaling.NewFromConfig(*c.awsConfig) } return c.autoscalingClient } // EC2Client returns the EC2 service client func (c *AWSClient) EC2Client(ctx context.Context) *ec2.Client { c.lock.Lock() defer c.lock.Unlock() if c.ec2Client == nil { c.ec2Client = ec2.NewFromConfig(*c.awsConfig) } return c.ec2Client } awsmini/internal/conns/config.go // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package conns import ( "context" "fmt" "time" awsbase "github.com/hashicorp/aws-sdk-go-base/v2" basediag "github.com/hashicorp/aws-sdk-go-base/v2/diag" "github.com/hashicorp/aws-sdk-go-base/v2/logging" ) // Config holds the provider configuration type Config struct { AccessKey string Profile string Region string SecretKey string SharedConfigFiles [] string SharedCredentialsFiles [] string SkipCredsValidation bool SkipRegionValidation bool SkipRequestingAccountId bool TerraformVersion string Token string } // ConfigureProvider configures and returns an AWS client func (c *Config) ConfigureProvider(ctx context.Context) (*AWSClient, error ) { ctx, logger := logging.NewTfLogger(ctx) const maxBackoff = 300 * time.Second awsbaseConfig := awsbase.Config{ AccessKey: c.AccessKey, APNInfo: &awsbase.APNInfo{ PartnerName: "HashiCorp" , Products: []awsbase.UserAgentProduct{ {Name: "Terraform" , Version: c.TerraformVersion, Comment: "+https://www.terraform.io" }, {Name: "terraform-provider-awsmini" , Version: "0.0.1" , Comment: "+https://github.com/torut/terraform-provider-awsmini" }, }, }, CallerDocumentationURL: "https://github.com/torut/terraform-provider-awsmini" , CallerName: "Terraform AWS Mini Provider" , Logger: logger, MaxBackoff: maxBackoff, MaxRetries: 25 , Profile: c.Profile, Region: c.Region, SecretKey: c.SecretKey, SkipCredsValidation: c.SkipCredsValidation, SkipRequestingAccountId: c.SkipRequestingAccountId, Token: c.Token, } if len (c.SharedConfigFiles) != 0 { awsbaseConfig.SharedConfigFiles = c.SharedConfigFiles } if len (c.SharedCredentialsFiles) != 0 { awsbaseConfig.SharedCredentialsFiles = c.SharedCredentialsFiles } // Get AWS configuration ctx, cfg, awsDiags := awsbase.GetAwsConfig(ctx, &awsbaseConfig) if awsDiags.HasError() { return nil , fmt.Errorf( "error configuring AWS: %s" , diagsToString(awsDiags)) } // Create AWS client client := &AWSClient{ awsConfig: &cfg, region: c.Region, } return client, nil } // diagsToString converts diagnostics to a string func diagsToString(diags basediag.Diagnostics) string { var result string for _, d := range diags { if result != "" { result += "; " } result += fmt.Sprintf( "%s: %s" , d.Summary(), d.Detail()) } return result } ビルドと設定 awsmini ディレクト リで以下のコマンドを実行します。 go build -o .\bin\terraform-provider-awsmini_v0.0.1.exe . 適切な場所( Windows ならユーザーの %APPDATA% ディレクト リ、 Mac ならユーザーのホーム ディレクト リ)に terraform.rc というファイルを作成して以下の内容を記述します。 provider_installation { dev_overrides { "local/awsmini" = "C:\\Users\\torut\\Desktop\\awsmini\\bin" } } これにより、Terraformが local/awsmini プロバイダーを指定されたローカルパスから読み込むようになります。(参考: Create a Terraform CLI configuration file ) dev_overridesだと terraform init が使用できないため注意してください。 filesystem_mirrorを指定すれば、 terraform init を使用してローカルのプロバイダーを参照できます。 Custom Providerでリソースを作った結果 以下のコードを実行してASGを作成しました。 terraform { required_version = ">= 1.6.0" required_providers { awsmini = { source = "local/awsmini" version = "0.0.1" } } } provider "awsmini" { region = "ap-northeast-1" } resource "awsmini_launch_template" "example" { name_prefix = "awsmini-lt-" image_id = "ami-0d52744d6551d851e" instance_type = "t3.micro" } resource "awsmini_autoscaling_group" "example" { name = "awsmini-asg-example" max_size = 3 desired_capacity = 2 min_size = 1 health_check_type = "EC2" vpc_zone_identifier = [ "subnet-0fcd0acfe38968bb2" , "subnet-017fa0e9eb9e46c84" ] launch_template { id = awsmini_launch_template.example.id # versionは未指定 } } 実行した結果、version未指定でASGの起動テンプレートのバージョンが $Latest として設定されていることを確認しました。 terraform plan の出力(一部) Terraform will perform the following actions: # awsmini_autoscaling_group.example will be created + resource "awsmini_autoscaling_group" "example" { + arn = (known after apply) + default_cooldown = (known after apply) + desired_capacity = 2 + health_check_grace_period = 300 + health_check_type = "EC2" + id = (known after apply) + max_size = 3 + min_size = 1 + name = "awsmini-asg-example" + vpc_zone_identifier = [ + "subnet-017fa0e9eb9e46c84", + "subnet-0fcd0acfe38968bb2", ] + launch_template { + id = (known after apply) + name = (known after apply) + version = "$Latest" } } マネジメントコンソール まとめ 今回は、公式の AWS Providerを参考にCustom Providerを実装し、 aws_autoscaling_group の launch_template ブロックの version をデフォルトで $Latest に設定できるようにしました。 実装を通して、Terraform Providerの内部構造や仕組みを理解し、公式 リポジトリ を読み解きながら拡張する経験を得られました。 今後、 AWS Provider に課題を感じた際は、今回の経験を活かして Custom Provider を実装することで課題を解決したり、さらに理解が深まれば AWS Provider へのコントリビュートにも挑戦してみたいと思います。 この記事が、Custom Provider 実装に取り組む方の一助になれば幸いです。 ここまでお読みいただき、ありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @ooka.toru レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
キャリア祭りとは? こんにちは。 電通 総研の金融IT本部の上野です。 普段の業務ではアーキテクトとして案件に関わる一方で、近年は生成AIを用いた開発生産性の向上についても力を入れて検証を行っております。 さて、10月に 電通 総研の社内イベントとして「キャリア祭り2025」なるイベントが開催されました。 以下が開催概要の引用となります。 「キャリア」という言葉は難しく捉えられがちですが、その根幹は道であり人生そのもので、皆が当たり前に持っているものです。そのキャリアが「自分らしくある」というのがポイントであり実に難しい点です。「自分らしい」とは、「自分らしい人生」とはなにか、「 電通 総研人としてなにがしたいか」キャリア祭りでそのヒントをみつけてください。 おそらく、「いろいろな人の話を聞いたり、自分と向き合ったりする中で、今後どのようなキャリアを歩んで行くかを考えてほしい」という、かなりふわっとした理解ですが、そういった意図があったのではないかと思います。 そのイベントの中で、ビッグネームの方が来社されてお話を聞く機会がありました。 講演内容としては以下のとおりです。 『t-wada』さん:「AI時代のソフトウェア開発を考える」 『中山ところてん』さん:「広聴AI技術解説 ブロードリスニングを支える技術」 それぞれパブリックに公開されている資料ではありますが、生の声を聴きながら、また質疑等も行い、いちエンジニアとしてとても刺激的な講演でした。受講者の反響もとても大きいものでした。 電通 総研で働くエンジニア、アーキテクトとしての今後のキャリアを考えていくきっかけとなりましたので、僭越ながら、こちらの講演を聞いたうえでの感想・ 現業 における考え方などについて共有できたらなと思います。 AI時代のソフトウェア開発を考える(t-wadaさん) こちらは、7月頃に初版が公開された資料を更新されたものです。 パブリックに公開されている ため、内容を知っている人も多いのではないでしょうか。 要約 1. 2025年は「Vibe Coding」熱 AIエージェントと強力なコード生成により動くものが出来上がるまでが爆速化。多くの人が動作確認だけで前に進み、コードレビューや設計の吟味が省かれがちという状況認識がイントロダクション。ポイントは以下のとおりであり、ソフトウェア開発の道具が変わっただけで本質的な問題は何も変わっていないという点が重要。 技術的負債の積み上がる速度が速くなっただけ で、負債をためない アーキテクチャ がソフトウェア開発として重要なのは変わっていない。 レビューが課題となることは以前からの課題 であり、新たに顕在化したものではない。 適切なドキュメントを書けばコーディングは些細な問題 というのは新たな考えではなく、これまで様々なツールの登場・衰退とともに何度も議論されてきた。 2. “Agentic Coding”の提案(スピード×健全性の両立) AI自走型の領域と、AI伴走型の領域を明確にすること。 AI自走型の領域 では、並列で実行するためスピードは上がるが、人のコン トロール や状況把握が難しくレビューが課題となる。業務ロジックが複雑でない領域はこのやり方。 AI伴走型の領域 では、人がコン トロール する領域が大きく、スピードは落ちるが直列に開発を進める領域。 ドメイン に特化し、ビジネスの中核となる領域ではこのやり方。 3. 自動化(automation)から 自働化 (autonomation)へ AI伴走型、AI自走型どちらの領域でも、AIは暴走/迷走する。そのためのガードレール設計が不可欠となる。 ガードレール技術としての適応度関数の重要性、その代表例としては自動テストがより重要に。 包括的な構成管理では、現在から近い将来はモノレポが有利であり、ドキュメントの同一 リポジトリ による管理が重要。 Unknown-Unknown領域が増えるため、バグゼロ信仰から離れ、 MTBF から MTTR へ切り替える。 レビュー負荷軽減の工夫として、Behavior ChangeとStructure Changeを明確にする。 こうした枠組みで “動く速さ” に “正しさ・保守性” をのせるべき。 4. 現場でどう使うか?またエンジニアはどうこの時流に立ち向かっていくべきか AIの活用 ここまで記載してきたように、開発に取り入れるにしてもガードレール設計を行ったうえでの開発が必要。そのためにはソフトウェアエンジニアリングが不可欠。 outputを出すためだけではなく、自分の能力を上げるためにもAIを利用する。 エンジニアとしてどう立ち向かっていくべきか AIは知識の代替ではなく 増幅器 であり、AIから引き出せる性能は自分の能力にそのまま比例する。 AIに賭けるのではなく、自分の能力を上げることは必然的に必須。あえてのオーガニックコーディング、AIを用いた議論。 感想 さて、要約といいつつ重要なポイントが多いため熱くなって色々書いてしまいました。 というのは、まさに今年は変化の大きい年であり、 現業 のやり方や顧客から求められるものも大きく変化したと感じていた中で、すっと腹に落ちる内容だったためです。 現業 でどう取り組んでいくか 私は 現業 では M5 というマイクロサービス用の フレームワーク 開発を行っており、生成AIが登場する前も「 ソフトウェアエンジニアリングの領域 」の観点で、開発の レバレッジ および保守性の向上のためにDDDや自動テストに取り組んでいました。 生成AIの登場により、「より開発生産性が高くなるためにはどうすればよいか?」を日々検討していました。 今回の講演を拝聴し、以下の観点で取り組んでいこうと考えています。 (1)これまですでに敷いていた「ガードレール」をAIネイティブにブラッシュアップし、より効果的な安全対策を実現することで特に「AI自走型」領域における開発速度を向上させる。 構成管理をモノレポに変更 AIが理解できるコンテキストを身近に配置し、ドキュメントから実装まで 一気通貫 で行えるような管理体制が必要となります。 ドキュメントをこれまで以上に Markdown で記載 例えばバックエンドであれば、インターフェースの定義(OpenAPI Spec等)、DBの定義(ER図)を Excel ではなくAIが理解できる形にします。ER図はmermaidでは保守性がいまいちになることもあり、現在模索中です。 業務 ドメイン が少ない領域でも、 ユビキタス 辞書による単純なエラーハンドリングは必要です。OpenAPI Specと JSON Schemaの統合により、インターフェース設計から単項目バリデーションの実装までを 一気通貫 で行います。 アーキテクチャ をより明確に DDDやクリーン アーキテクチャ というワードは思想であり アーキテクチャ レベルでは曖昧であるため、DDDのうち採用している要素をAIに理解させるために明確にレイヤ化された アーキテクチャ を明記する必要があります。これまでは人が読むために図式化して記載していた アーキテクチャ を、Agentが理解できるように書き換えていきます。 実装の手順・テストの手順をより明確に 例えばClaude CodeであればCLAUDE.mdに、実装手順・テスト手順を明確に指示することが必要になってきます。これまで規約として管理してきた実装手順では、 ドメイン の実装→ ドメイン のテスト→プレゼンテーション層→プレゼンテーション層のテストと進むことで、低レイヤから品質を担保しながら実装を進めることができています。これを、Agentに理解できるように書き換えていきます。 以下が、CLAUDE.mdの例として開発手順を記載した例です。 ## Development Process ### Layered Development Order **IMPORTANT**: When implementing a new feature, ALWAYS follow this development order: 1. **Domain Layer** → 2. **Infrastructure Layer** → 3. **API Layer** This order ensures that: - Business logic is validated first without external dependencies - Database operations are tested before exposing them via API - The complete feature is built on a solid, tested foundation ### Step 1: Domain Layer (No Side Effects) **Implementation Order:** 1. Value Objects (`domain/value/`) - **String-based**: Extend M5 framework base classes ...略 (2)「AI伴走型」領域では、人間がAIを活用しやすい環境を整備する。 AIによるレビューはマスト twadaさんも「コーディングが開発の ボトルネック だったことはなく、いつも ボトルネック はレビュー」とおっしゃっていましたが、レビューの負荷を下げていくことは必須です。例えば GitHub Copilotによる一次レビューにより、人がこれまで判断していた typo やコメントの誤記、不要な変数などを除去してくれます。linterやformatterの整備は当然ですが、そのうえでコードの可読性や保守性を高めるための細かな改善点まで指摘してくれるため、併用することでレビュワーの負荷を下げます。 設計伴走 新規に採用する OSS やテスト手法(利用ツール)の変更など、ARDとしてAIと議論、決定。 (業務 ドメイン が複雑な領域について)伝えた要件から Markdown を記載し、人の手で最終的にFIXさせる。フォーマットは人間の手で整備し、準拠させる。 テスト伴走 自身が大量に書いていた JUnit やテストデータについて、コードから検証すべき内容を自動生成し、テストコードの作成を効率化。 逆に、詳細設計からテストコードを出力し、TDDの手法によりグリーンになることを人間とAgentが並走して解消する。最初からテストコードを全量作成するのは不可能なので、漏れているケースについては追加し、レッド→グリーンのサイクルを回す。 ナレッジの蓄積 ここまでの運用で失敗した内容を、逐次AIが理解できるドキュメントに再度追記する運用の定義。 総括 書いていて感じましたが、今までソフトウェアエンジニアリングとしてやってきたことと大きくは変わりません。これらを、AIネイティブに変更していくだけです。また、このガードレールを整備していくには自身のスキルを向上させることが必須となります。 AIにすべてベットするのではなく、自身のスキル向上をAIによってサポートしてもらいながら、 現業 で利用できるところは利用していこうという欲張りな感想を記載して締めとします。 広聴AI技術解説 ブロードリスニングを支える技術(中山ところてんさん) こちらも、 すでに公開されている 資料をもとに講演していただいた内容です。 ところてんさんは 広聴AI の開発に関わっており、広聴AIの技術要素の説明を行っていただきました。 要約 1. 広聴AIとは(背景と目的) デジタル民主主義2030が開発する OSS の意見可視化・分析(ブロードリスニング)基盤。市民の自由記述を“論点単位”に分割し、 多様な意見(少数意見も含む) を俯瞰できるようにする。東京都の政策検討などでも活用が進んでいる。 本資料の狙いは、普通の プログラマ でも理解できる技術水準で、ベクトル化・LLM次元圧縮・ クラスタリング ・ラベリングのつなぎ方を解説すること。 2. 処理パイプライン * 抽出(意見分割):長文から論点をLLMで分割し、構造化( JSON )。分割ではFewshotにより分割の基準が明確化、出力結果が安定。 埋め込み:分割済みテキストを文脈ベクトル化。 意見グループ化: UMAP(2次元化)→k-means(細分割)→Ward法(統合) で階層的 クラスタリング 。 初期ラベリング: KJ法 /表札 という専門用語によって、各 クラスタ の特徴を抽出し、意味のあるラベルを付与。 統合ラベリング:下位ラベルを束ねて上位 クラスタ の名称・説明を決定。 要約:上位ラベル群から全体概要をLLMで1段落に圧縮。 出力:中間成果を1つの JSON に集約。ビジュアライズのための準備。 表示:現生成された Json をもとにnodeで静的ウェブページを作成する。(現在は未使用)。 3. TTTCとの関係と アルゴリズム の選択 広聴AIはTTTC( Talk to the City)由来だが、説明容易性重視の構成に最適化。 広聴AI:Embedding→UMAP→k-means→Ward→LLMにより クラスタ に 命名 。 TTTC:BERTopic(BERT+HDBSCAN)系、またはUMAP→Spectral Clustering系の2通り。 4. まとめ LLMやエンベディングをプログラムに組み込むことで、複雑な 自然言語 を処理できるようになり、新たなサービスを作るチャンス。 感想 LLMを分割/ 命名 /要約の道具として割り切っており、データサイエンスでこれまで使われてきたEmbedding, UMAP, クラスタリング による実装骨格が作られている点が非常に面白かったです。日常の業務ではやはりどうしても「LLM(生成AI)によって業務を最適化してほしい」といった方向に思考が行きがちだったため、 目から鱗 でした。 この技術を応用してどんなサービスが考えられるか? さて、なかなか 現業 でどのように取り組んでいくかは難しいところなので、LLMやエンベディングにより今後どのようなサービスが考えられるか?いくつか案でも考えてみます。 サービス問い合わせの論点 クラスタ ダッシュ ボード 「広聴AI」とほぼ同様の仕組みで実現可能。 問い合わせに応じてラベリング、 クラスタ を分割。 技術的要因の場合は「問い合わせする人」では判別しづらいこともあり、カテゴリを自然に分割することでサービス管理者の負荷を軽減。 例えば システムプロ ンプトの例としては以下。 # 役割 あなたは「サービス問い合わせの論点クラスタダッシュボード」を生成するアナリストAIです。 ユーザー問い合わせ(自由記述)を機械可読なクラスタと安定ラベルへ整理し、担当者の負荷を下げるための カテゴリ分割・優先度付け・エスカレーション指針を生成します。 # 目的(達成基準) 1) 問い合わせ文を「論点単位」へ分割し、重複・テンプレ表現を正規化して**安定クラスタ**を形成する 2) 各クラスタに**説明可能な表札(ラベル)**と**原因仮説**、**推奨アクション**を付与する 3) 技術的要因でユーザー本人が分類しづらい案件を**自然なカテゴリ**に分割し、**担当ルーティング**を明確化する # 入力 - 略 # 出力(最終)— JSON - 略 運用ログの 自然言語 要約 システム運用において膨大に蓄積されるログについて、 自然言語 による要約+ラベルを付与。 マイクロサービスにおいては、負荷のかかっているサービスの原因を可視化。 アラートの抑制として、ただあるログが検知された際に発火していたアラートをより業務ロジックよりに昇華。 例えば システムプロ ンプトの例としては以下。 # 役割 あなたはSRE/プラットフォームチームの“ログ理解・負荷原因可視化・アラート抑制”コパイロットです。 分散トレース/ログ/メトリクスを、ビジネス影響が判断できる形に要約し、再現性のあるラベル付けと運用アクション提案を行います。 # 目的 1) 膨大なログを「インシデント単位」に自然言語で要約し、安定ラベル(表札)を付与 2) マイクロサービスで負荷が高いサービスの**第一原因**を可視化(依存関係・SLO寄与込み) 3) 単純な“文言検知”型アラートを、**業務ロジック寄りの条件**に昇華(抑制/バンドル/昇格) # 入力(機械可読) ```json { "traceId": "9999999" "ts":"99-99-99T99:99:99Z", "service":"auth", "endpoint":"POST /sign-in", "status":403, ... 以下略 } 総括 いくつか案を出しましたが、微妙だと思われるかもしれません。しかし、LLMと クラスタリング の組み合わせにより考えられるサービスの幅が広がった気がします。 プロジェクト運用の中でも利用シーンがあればぜひ使ってみたいと思える技術要素でした。 おわりに 「キャリア祭り」というタイトルに即して感想を述べるなら、私自身、生成AIの登場により日々どのように 現業 と向き合っていくか悩みながら仕事をしていました。 「エンジニアとして生きていけるのか?」、「この領域はAIに取って代わられてしまうのではないか?」そう感じている同業種の方は少なくないと思います。 しかし、今回の講演を拝聴し、エンジニアとして研鑽することとAIツールを使いこなすことの両輪でキャリアを歩んでいこうと強く思えました。 AIの登場により、 自身の日々のエンジニアリング業務 にも、また 今後出てくるAIを利用したサービス にも大きな変化が生まれてきます。常に時代の変化に適応していくことが大切だと再認識させられました。 AIライフ楽しんでいきましょう、ご講演ありがとうございました! 私たちは一緒に働いてくれる仲間を募集しています! 新卒採用ページ クラウドアーキテクト 執筆: @kamino.shunichiro レビュー: @nagamatsu.yuji ( Shodo で執筆されました )
はじめに こんにちは、クロス イノベーション 本部エンジニアリングテク ノロ ジー センターの小澤英泰です。 現在、サンフランシスコで開催中の GitHub Universe 2025に2年連続で現地参加しています。生成AIが開発現場に浸透する中、 GitHub Copilotをはじめとする GitHub の進化は、多くの開発者にとって見逃せないトピックとなっています。 Keynote では開発者体験を向上させる重要な発表が複数あり、会場の興奮が冷めないうちに速報としてお届けします。 ⚠️ 速報記事について この記事は Keynote 終了直後に現地から速報としてお届けしています。内容の正確性については公式情報を確認してください。一部、聞き取りや理解に誤りがある可能性があります。 開催概要 イベント名: GitHub Universe 2025 日程:2025年10月28日(Day 1)、29日(Day 2)※現地時間 場所:サンフランシスコ(フォート・メイソン・センター) 会場の様子 入口 会場 Keynote Keynote は GitHub COOのKyle Daigle氏を中心に進行し、 GitHub Staff Developer AdvocateのJessica Deen 氏、Anthropic Chief Product OfficerのMike Krieger氏、OpenAI Codex Product LeadのAlexander Embricos氏、 Microsoft VS Code PM LeadのPierce Boggan氏、そして最後には Microsoft CEOのSatya Nadella氏が登壇しました。 「A new era of Collaboration」というテーマのもと、 GitHub が描く未来について次々と発表が行われ、最新機能のライブデモが披露されました。その中でも特に反響が大きかった発表を4つ紹介します。 主要発表 1. Agent HQ Agent HQとはAgentを GitHub プラットフォームに統合する仕組みです。今後、Anthropic、OpenAI、 Google 、Cognition、xAIなどのコーディングエージェントが GitHub 内で利用可能になります。 デモでは、IssueのAssigneesにClaudeを指定すると実装からPull Requestの作成まで自動で行う様子や、Codexに4つのタスクを並行実行させる様子が披露され、会場は大きな盛り上がりを見せました。 2. VS Code の GitHub CopilotのPlanモード VS Code の GitHub Copilotチャットパネルでは今までAsk、Edit、Agentの3つのモードのみでしたが、Planが追加されました。 実装の着手前に計画を立て、必要に応じてCopilotからの追加質問に回答できるのはとても良い体験ですね。 3. Copilot review GitHub Advanced Security(GHAS)の一部機能が GitHub Copilotの中に統合される形でセキュリティに関する能力が向上しました。 従来はGHAS契約下でCopilot Autofixを利用していましたが、今回の発表では GitHub Copilot側にセキュリティ機能が取り込まれ、GHAS非契約でも一部機能が使えると理解しました。これにより、従来のようなセキュリティ専門家のペルソナを自前で与え管理する作業が不要になります。 4. Copilot Metrics GitHub Copilotの生産性への影響の計測は組織課題の1つではないでしょうか。従来は GitHub Copilot Metrics API を自身で扱う、または、 サードパーティ のツールを導入していた企業もあるかと思います。 GitHub からの提供はありがたいですね。機能の充足性が気になります。 所感 今回の Keynote で最も印象的だったのは、Nadella氏が Microsoft と GitHub の今後10年間の関係についての質問に対し、 GitHub がオープンなプラットフォームであることを強調していたことです。 GitHub CEO の Thomas Dohmke 氏が 2025 年 8 月に退任の意向を発表しましたが、 GitHub の立ち位置は変わらないというメッセージだと私は受け取りました。 それでは引き続き、詳細なセッションへの参加と、 ヒアリ ングを通じて理解を深めてまいります。 詳細情報をいち早く確認したい方は、以下の公式情報をご参照ください。 Introducing Agent HQ: Any agent, any way you work 番外編:現地イベント GitHub Universe は最新技術の発表だけでなく、世界中の開発者コミュニティとの交流も大きな魅力です。 メインイベントのDay 1、Day 2の夜にはスポンサー企業によるミートアップが開催されます。加えて、Day 0とDay 3には以下のような特別なイベントも用意されています。 Day 0: JAPAN NIGHT Day 0のJAPAN NIGHTでは、20名超の開発者が参加し、 GitHub Copilotの全社展開や実際のプロジェクトへの適用事例について意見交換を行いました。また、 GitHub や Microsoft 社員の方々からもGHASの活用方法もお聞きすることができました。 店内のテレビでは、 ワールドシリーズ 第3戦が18イニングの延長戦に突入する歴史的な熱戦が繰り広げられていましたが、それに負けない熱量で GitHub について語り合い、大いに盛り上がった一夜となりました。 Day 3: GitHub 本社限定イベント Day 3には GitHub 本社でのツアーや認定試験、製品デモ、コンテストなど、現地ならではのイベントが予定されています。こちらも楽しみです。 さいごに 以上、 GitHub Universe 2025の Keynote 速報をお届けしました。最後までお読みいただきありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @ozawa.hideyasu レビュー: @mizuno.kazuhiro ( Shodo で執筆されました )
はじめに 金融IT本部 2年目の坂江 克斗です。 ネットワーク分野に興味があり、業務を進めていくうちにネットワークセキュリティに関して腹落ちする部分が増えてきたため、概要をまとめて記事にしました。 初学者の視点で疑問に感じる部分も含め、基本的な概念から丁寧に解説できればと思います。 経歴 大学-大学院 建築⇒制御工学 IT知識はほぼ無し 入社1~2年目 AWS インフラの業務 資格 IPA : 基本情報、応用情報 AWS : CP, SAA, SAP, ANS, SCS はじめに 経歴 セキュリティの中でのネットワークの位置づけ ふわっとした疑問 レイヤーの考え方 AWSにおけるネットワークセキュリティ 前提 代表的なAWSサービス 構成例 (外部公開するWebアプリケーション) おわりに セキュリティの中でのネットワークの位置づけ 実際にハマったポイントを基に、ネットワークセキュリティの基本的な考え方を整理します。 ※ 実際は、コストや非機能要件によって実装すべきセキュリティレベルや内容を設定するものなので、あくまで学習の流れとして捉えていただけると嬉しいです ふわっとした疑問 AWS 資格の勉強を始めてから、セキュリティ強化の方法について考える機会が増えました。 AWS でセキュリティを高める手段として、例えば以下のようなものが思い浮かびます。 IAMの権限を最小限にする セキュリティグループの許可ルールを絞る WAFを導入する ACM で証明書を発行して SSL 接続を有効化する RDSを暗号化する Secrets Managerに機密情報を保存する etc... 一見すると、これらはすべて「セキュリティ対策」として並列に扱われがちですが、 実際に設計や運用を考えると 「どのサービスを、どんな場面で使い分けるべきか?」 という疑問が生じやすいと感じています。 例えば、DBの暗号化や機密情報の専用サービスによる管理はイメージしやすいですが、 アクセス制御に関しては次のような疑問が出てきます。 IAMとセキュリティグループはどちらも「アクセス制御」の役割があるように見えるが、どちらか一方だけで十分なのでは? セキュリティグループは ファイアウォール の役割を果たしているが、 AWS WAFも「 ファイアウォール 」と名が付いている。両方必要なの? このような疑問の根本的な原因は、 「各 AWS サービスが、アクセス時に発生する通信のどのレイヤーでセキュリティを提供しているのか」 を十分に理解できていないことにあります。 この点を整理するためには、 基本情報技術者試験 などでも学ぶ OSI参照モデル の考え方 が非常に役立ちます。 レイヤーの考え方 OSI参照モデル は、ネットワーク通信を7つの階層に分けて理解するための フレームワーク です。 例えば、セキュリティグループは ファイアウォール として IPアドレス やポートに基づくフィルタリングを行うため、 ネットワーク層 ・ トランスポート層 (第3・4層)に該当します。一方、IAMは認証・認可を担い、HTTPや HTTPS などのアプリケーション層(第7層)で制御を行います。 パケットは 低層のレイヤーから順に 評価されるため、セキュリティグループ(第3・4層)でパケットを拒否した場合、IAM(第7層)による認証・認可は実行すらされません。イメージとしては、山から下りてくるクマを堰き止めるのがセキュリティグループで、街に降りてきたクマの中でも家や畑に侵入してくる(特定の行動をする)クマだけを止めるのがIAMとなります。 ただし、低層のネットワークで排除する = 上層のセキュリティが不要なわけではなく、ネットワークだけでは管理できない処理(リソースには到達できるが、削除処理は拒否したい等)や 多層防御 の考え方が重要となります。 クラウド 環境では、物理的なサーバ管理等は クラウド プロバイダー側の責務となり、 ユーザは主に ネットワーク層 (第3層)から上位のレイヤーを制御 するため、ネットワークセキュリティが最も根本的かつ重要な防御レイヤーとなります。 躓きポイントとしてIAMとセキュリティグループを比較しましたが、後述するネットワークセキュリティにおける代表的な AWS サービスの表にIAMを入れていません。 現在の私自身の考えとしては、ネットワークセキュリティは 第2~4層で管理するリソースへの到達・接続可否の判定 や、 第2~7層における通信の暗号化 、認証・認可 (段階的な権限管理)というよりは 単純な評価・振り分けに留まる7層までの判定 が主と捉えています。 一方で、IAMはネットワークセキュリティとは異なり、 認証・認可や運用面での権限管理 を目的としたサービスであると考えています。 AWS におけるネットワークセキュリティ 前提 繰り返しになりますが、物理的な配線・サーバ管理等は AWS 側の責務となります。 AWS が提供する グローバルネットワーク では、有線接続かつ多段階の暗号化による通信保護も提供されており、 AWS のネットワーク内では 高速かつセキュア な通信を提供しています。 AWSグローバルネットワークを活用したAWSサービス例:AWS Global Accelerator AWS Global Acceleratorは、 TCP や UDP で提供されるアプリケーションに対して、世界中のクライアントからのアクセスを高速化するサービスです。 通常、 VPC でデプロイしたパブリック IPアドレス にアクセスする場合、ユーザーの通信はインターネット上のさまざまな外部ネットワークを経由して AWS に到達します。 この経路は、距離やネットワーク状況によって遅延やセキュリティリスクが発生することがあります。 一方、 AWS Global Acceleratorを利用すると、 Anycast IPアドレス を持つ専用のエンドポイントが世界中に配置されます。 ユーザーは最寄りのエンドポイントにアクセスすることで、通信がすぐに AWS のグローバルネットワークに入り、その後は AWS 内部の高速かつセキュアなネットワークを通じて目的のリソースに到達します。 これにより、 通信経路の最適化による 遅延の減少 外部ネットワークを極力避けることによる セキュリティ向上 が実現できます。 代表的な AWS サービス 基本的なネットワークセキュリティの概念および対応する代表的な AWS サービスは、以下のように分類できます。 カテゴリ サービス / 機能 レイヤー 説明・具体例 セグメント分割 Amazon VPC (サブネット、ルートテーブル) 3 サブネットを役割ごとに分割し、ルートテーブルで通信経路を制御します。 必要なリソース数に応じて適切なサイズの CIDRブロックを割り当て 、 ルートテーブル でサブネットの通信経路を制限します。 ファイアウォール ネットワーク ACL 3~4 サブネット単位で設定するステートレス型のフィルタ。送信・受信を個別に判定し、帰り通信では エフェメラ ルポートに注意が必要です。 セキュリティグループ 3~4 リソース単位で設定するステートフル型のフィルタ。送信が許可されていれば戻り通信も自動的に許可され、 IPアドレス ・ポートだけでなく他のセキュリティグループも対象に設定できます。 IDS / IPS(不正侵入検知・防止) AWS Network Firewall 3~7 パケットインスペクションにより トラフィック を制御。高度なルール設定により、 ファイアウォール より柔軟な制御が可能です。 AWS Gateway Load Balancer 3~7 サードパーティ 製のIDS/IPSに中継するためのトンネリングを提供します。Network Firewall も本サービスを内部で使用し動作します。 NATによるIP秘匿 NAT ゲートウェイ / NAT インスタンス 3~4 外部から隔離されたプライベートサブネット内のリソースが、外部通信を行う際に自身のIPを秘匿しつつ通信可能にします。 EC2/Fargateによる自作NAT インスタンス 3~4 カスタム構成によるNAT。セキュリティ設定や運用管理が必要です。 プロキシによる 通信制 御・中継 ALB (Application Load Balancer) 7 負荷分散および7層情報を基にしたルーティングが目的となります。ネットワーク面では、内部通信を中継することで内部リソースを外部ネットワークから隔離できます。 API Gateway 7 クライアントの API リク エス トを受け取り、バックエンドへ中継します。ネットワーク面では、内部通信を中継することで内部リソースを外部ネットワークから隔離できます。 EC2/Fargateによる自作プロキシ 7 nginxなどを利用して独自に構成するプロキシ。柔軟な 通信制 御や迅速なログ取得が可能です。 暗号化・署名 MACsec 2 ネットワークフレームを暗号化し、2層での盗聴や改ざんを防止します。 AWS ではDirect Connectで使用可能です。 VPN ( IPsec 等) 3 or 4 トンネルモードでは第3層以上、トランスポートモードでは第4層以上を暗号化します。 AWS ではClient VPN やSite-to-Site VPN もしくは、EC2等で独自構成することが可能です。 TLS / SSL 4,7の間 AWS では ACM により証明書の発行・管理を行い、ALBやCloudFront、RDS等に証明書を設定することで、 HTTPS 通信を実現できます。 DNSSEC - Route53で設定可能。 DNS 応答に署名検証を追加し、改ざんやなりすましを防止します。 セキュリティ攻撃からの防御 AWS Shield 3~4 or 3~7 DDoS攻撃 からの防御を提供するマネージドサービス。Standardは第3~4層、Advancedは第7層まで対応し保護します。 インバウンド専用。 AWS WAF 3~7 HTTP/ HTTPS 通信の ペイロード を確認し、 SQLインジェクション や XSS などの攻撃を防御します。IPやアクセスレート、地理ベースでの防御も可能です。 インバウンド専用。 包括的サービス Amazon GuardDuty - 各種ログを分析し、異常な挙動や脅威を検知します。 AWS Security Hub - 各 AWS サービスのセキュリティ情報を集約し、 AWS 推奨構成との整合性を評価します。 AWS Shield Network Security Director - ネットワーク構成の 脆弱性 を包括的に評価し、リスクを可視化します。 その他 VPC Endpoint 3~7 外部ネットワークにアクセスせずに、 VPC 内部から AWS サービスにアクセス可能。 エンドポイントポリシーにより、権限的なアクセス管理も可能。 各サービスは、その設計目的や役割の違いにより、 インバウンド/アウトバウンド通信の制限、構築・運用負荷、レイヤーの違い 等の特性が異なります。 また、同じレイヤーに位置するサービスであっても、評価や制御が行われるタイミングが異なる場合があります。 そのため、 これらの特性を正しく理解したうえで、非機能要件やコストに応じて適切に組み合わせることが重要です。 構成例 (外部公開するWebアプリケーション) 前節で紹介したネットワークセキュリティの考え方を踏まえ、 AWS 上で外部公開するWebアプリケーション の構成例を図示します。 カテゴリ 説明・具体例 セグメント分割 サブネットを「パブリック」「Network Firewall 」「アプリケーション」「データベース」など役割ごとに分割し、不要な経路を遮断。最小限の通信経路だけを許可します。 ファイアウォール ネットワーク ACL やセキュリティグループで、サブネットやリソースごとに通信元・通信先・ポートを制限。図の矢印の経路以外は遮断します。 IDS / IPS(不正侵入検知・防止) 外部へのアウトバウンド通信時に、 ファイアウォール だけでは防げない ドメイン ベースのフィルタや不正な通信の検知・遮断を実施。 NATによるIP秘匿 アプリケーション(ECS/Fargate)が外部サービスへアクセスする際、NAT ゲートウェイ を経由してプライベートIPを隠蔽しつつ通信します。 プロキシによる 通信制 御・中継 ALB(Application Load Balancer)が外部からの入口となり、アプリケーション本体(ECS/Fargate)は直接インターネットに公開しません。 暗号化・署名 ALBに SSL / TLS 証明書を設定し、クライアント~ALB間の通信を暗号化。盗聴や改ざんを防ぎます。 セキュリティ攻撃からの防御 AWS WAFや AWS Shieldでインバウンド通信におけるDDoSやWebアプリケーションへの攻撃( SQLインジェクション 、 XSS 等)を防御します。 今回は自分に馴染みのある AWS 構成図をピックアップしましたが、例えば要件に合わせて以下のように更新することもできます。 外部サービスへのアクセス制限が不要な場合は、Network Firewall を導入せずにNACLやセキュリティグループのみで構成する。 社内プロキシからのアクセスに限定したい場合は、プロキシの グローバルIPアドレス を基に、 AWS WAFのIPセットや ファイアウォール で制限をかける。 おわりに 今回の記事を通じて、ネットワーク分野に興味を持つ方が少しでも増えれば嬉しいです。 私自身まだまだ知らないことばかりなので、今後もこのような形でアウトプットしながら、使用方法だけでなく基本的な概念も含めて理解を深めていきたいと思います。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト
はじめに エンタープライズ 第一本部、2025 Japan AWS Jr. Champions の佐藤悠です。 私は Kubernetes を触る機会が多く、その中でも監視に最近興味を持っています。 監視を実現するセキュリティソリューションの中に Tetragon などが挙げられますが、この監視のベースとなっている技術にeBPFがあります。 このeBPFをEC2 インスタンス の Amazon Linux 上で動かして、その面白さと何が起きているのかを解説します。 はじめに Linuxユーザー空間/カーネル空間 カーネルモジュール 環境 カーネルヘッダのインストール ソースコードの保存 Makefileの作成 モジュールのビルド モジュールのロード モジュールのリスト モジュールのアンロード 感想 ようやく、eBPF eBPF Verifier 呼び出せるカーネル関数はかなり限定的 Stepの制限 メモリアクセスの制限 NULLポインタ参照がないか eBPFでHello World 環境 ファイルの作成 実行 結果 感想 そしてTetragonへ... おわりに Linux ユーザー空間/ カーネル 空間 まずはeBPFの背景知識として Linux のユーザー空間と カーネル 空間に関して理解する必要があります 下図のように Linux にはユーザー空間と カーネル 空間というものが存在しています。 これは一度 カーネル を経由させることで物理デ バイス へのアクセスを制御するのと共に、プロセスが利用するリソースの一元管理を行い配分をする狙いがあります。 そのためユーザーと カーネル の空間を区切り、 システムコール というインターフェースでのみ カーネル へアクセスを強制し、アクセス制御とリソース配分を漏れなく実現しているというわけです。 以下はlsコマンドを実行したときに呼び出されている システムコール です。 さまざまな システムコール を組み合わせて カーネル にアクセスして、コマンドの実行結果を出力していることが理解できますね。 $strace -c ls % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 7 read 0.00 0.000000 0 3 write 0.00 0.000000 0 23 close 0.00 0.000000 0 30 mmap 0.00 0.000000 0 7 mprotect 0.00 0.000000 0 1 munmap 0.00 0.000000 0 3 brk 0.00 0.000000 0 2 ioctl 0.00 0.000000 0 4 pread64 0.00 0.000000 0 2 2 access 0.00 0.000000 0 1 execve 0.00 0.000000 0 2 2 statfs 0.00 0.000000 0 2 1 arch_prctl 0.00 0.000000 0 1 futex 0.00 0.000000 0 2 getdents64 0.00 0.000000 0 1 set_tid_address 0.00 0.000000 0 34 13 openat 0.00 0.000000 0 22 newfstatat 0.00 0.000000 0 1 set_robust_list 0.00 0.000000 0 1 prlimit64 0.00 0.000000 0 1 getrandom 0.00 0.000000 0 1 rseq ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 0 151 18 total ここでプロセスは カーネル を経由するから、アプリケーションの振る舞いは カーネル を見ていれば監視できるのでは?という気づきがあるわけです。 カーネル モジュール じゃあ「 Linux の カーネル にコードの変更をしよう!」と思っても、 Linux の利用されている規模、そもそものコード量などを鑑みると、コミュニティの理解を得て、開発のコストをかけて、アップストリームになるまで待つという気の遠くなる時間がかかる作業となってしまいます。 この問題に対して、 Linux は カーネル の変更・拡張のためにモジュールを受け入れるようにできています。 これが カーネル モジュールの概念です。 では、 カーネル モジュールを作成してみます。 私は C言語 の経験がないので以下の書籍「 Linux Device Drivers, 3rd Edition」のChapter2 p16 The Hello world Moduleの項を参考にしました。 参考: https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch02.html 適当に変更してgogo.cを作成します。 以下のモジュールを カーネル にロードするとgo!go!、アンロードするとbyebye!とログバッファに出力します。 #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE ( "GPL" ); //ロードされたら実行される関数 static int gogo ( void ) { printk (KERN_ALERT "go!go! \n " ); return 0 ; } //アンロードされたら実行される関数 static void byebye ( void ) { printk (KERN_ALERT "byebye! \n " ); } module_init (gogo); module_exit (byebye); これが今回のモジュールです。 ではロード/アンロードしてみます。 環境 アウトバウンド443ポートの通信のみを許可したサブネット内です。 EC2 インスタンス で Amazon linux を起動し、SessionManagerで接続をしています。 sh-5.2$ cat /etc/os-release NAME="Amazon Linux" VERSION="2023" ID="amzn" ID_LIKE="fedora" VERSION_ID="2023" PLATFORM_ID="platform:al2023" PRETTY_NAME="Amazon Linux 2023.8.20250804" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023" HOME_URL="https://aws.amazon.com/linux/amazon-linux-2023/" DOCUMENTATION_URL="https://docs.aws.amazon.com/linux/" SUPPORT_URL="https://aws.amazon.com/premiumsupport/" BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023" VENDOR_NAME="AWS" VENDOR_URL="https://aws.amazon.com/" SUPPORT_END="2029-06-30" カーネル ヘッダのインストール sudo dnf install gcc make kernel-devel kernel-headers Last metadata expiration check: 1:37:35 ago on Sat Oct 18 05:49:32 2025. Package gcc-11.5.0-5.amzn2023.0.4.x86_64 is already installed. Package make-1:4.3-5.amzn2023.0.2.x86_64 is already installed. Package kernel-devel-1:6.1.147-172.259.amzn2023.x86_64 is already installed. Package kernel-headers-1:6.1.147-172.259.amzn2023.x86_64 is already installed. Dependencies resolved. Nothing to do. Complete! ソースコード の保存 #先ほどのファイルをコピペ sh-5.2$ sudo vim gogo.c sh-5.2$ cat gogo.c #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE("GPL"); //ロードされたら実行される関数 static int gogo(void) { printk(KERN_ALERT "go!go!\n"); return 0; } //アンロードされたら実行される関数 static void byebye(void) { printk(KERN_ALERT "byebye!\n"); } module_init(gogo); module_exit(byebye); Makefile の作成 同じ ディレクト リで Makefile を作成 sh-5.2$ sudo vim Makefile sh-5.2$ sudo cat Makefile obj-m += gogo.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean モジュールのビルド make gogo.koが出来ました。 モジュールのロード ※私にはこの末尾のログは紫に光って見えています。 sh-5.2$ sudo insmod gogo.ko sh-5.2$ sudo dmesg | tail ... [ 6689.540149] gogo: loading out-of-tree module taints kernel. [ 6689.540977] gogo: module verification failed: signature and/or required key missing - tainting kernel [ 6689.542802] go!go! モジュールのリスト lsmodで確認しました。gogoモジュールがあります。 sh-5.2$ lsmod Module Size Used by gogo 16384 0 ・・・ モジュールのアンロード sh-5.2$ sudo rmmod gogo sh-5.2$ sudo dmesg | tail ... [ 6689.540149] gogo: loading out-of-tree module taints kernel. [ 6689.540977] gogo: module verification failed: signature and/or required key missing - tainting kernel [ 6689.542802] go!go! [ 6999.290169] byebye! 感想 こんなに手軽に カーネル が拡張できるんですね。 ところで、 カーネル モジュールをロードしたタイミングで以下のログが出ています。 loading out-of-tree module taints kernel. ツリー外(公式外)のモジュールをロードしたため、 カーネル が汚染されました。(翻訳:ChatGPT) まあ自作のモジュールなので、メッセージの通りですね。 ただ、安易なモジュールのロードって自作や第 三者 の提供、問わず カーネル が壊れる可能性がありリスクが大きそうだと思えてきます。 ようやく、eBPF そんな カーネル モジュールの代わりに登場するのが eBPF です。 eBPFは カーネル の ソースコード の改変や、 カーネル モジュールのロードを必要とせずに、安全かつ効率的な カーネル の機能拡張に利用されています。 参考: https://ebpf.io/ja/what-is-ebpf/ この 安全な拡張 は主にeBPF Verifierによって保障されています eBPF Verifier eBPF プログラムはクラッシュしたりシステムに悪影響を及ぼしたりしません。 プログラムは常に実行が完了します(つまり、プログラムは無限ループに陥って処理が実行し続けられることはありません)。 参考: https://ebpf.io/ja/what-is-ebpf/ このeBPF Verifier(検証器)でチェックしている内容は多岐にわたっているため、私が調査した範囲で代表的な項目を以下に列挙します。 呼び出せる カーネル 関数はかなり限定的 基本的には安全性が検証されたヘルパー関数しか使えません。 ヘルパー関数でも関係外の関数を呼ぼうとするとエラーとなります。 ※BPFプログラムタイプの概念 Stepの制限 100万Stepの指示数に以内の時に限り許可することで、CPUを占有しないようにしています メモリアクセスの制限 アクセス可能な範囲外のメモリに触れません。 ※eBPF mapという概念を使用して カーネル とユーザー空間でデータのやり取りをします。 NULLポインタ参照がないか    ( ・∀・)   | | ガッ   と    )    | |     Y /ノ    人      / )    <  >__Λ∩    _/し' //. V`Д´)/   (_フ彡        / これ以上にも、もっとたくさんの検証事があって安全性を保障しています。 ここに踏み込むと、分からないことだらけになってしまうので、「さまざまな検証を経て、 カーネル 空間での実行を許される」としておきます。 eBPFで Hello World ではeBPFプログラムの実装をします。 これは以下の書籍「入門eBPF」2章 p15 eBPFの「 Hello World 」を参考にしています。 参考: https://www.oreilly.co.jp/books/9784814400560/ BCC の Python ライブラリで C言語 で書いたeBPFコードを バイトコード へ変換し、 カーネル 空間にロードさせます。 #!/usr/bin/python from bcc import BPF program = r""" int hello(void *ctx){ bpf_trace_printk("Hello World!\\n"); return 0; } """ b = BPF(text=program) syscall = b.get_syscall_fnname( "execve" ) b.attach_kprobe(event=syscall, fn_name= "hello" ) b.trace_print() それぞれ分解しながら解説します。 以下のコードが カーネル にロードされるeBPFコードです。 program = r""" int hello(void *ctx){ bpf_trace_printk("Hello World!\\n"); return 0; } """ eBPFのプログラム作成時には、先述のヘルパー関数の1つ、bpf_trace_printk()を用いて出力していることが分かります。 この C言語 を コンパイル して動作するようにします。 b = BPF(text=program) 以上のように書くことで、BPFオブジェクトの生成時に引数に渡すことで BCC に コンパイル させます。 syscall = b.get_syscall_fnname( "execve" ) 次のコードでバイナリのパスを渡して実行する システムコール 「execve()」の関数名を取得します。 b.attach_kprobe(event=syscall, fn_name= "hello" ) ここでexecve()に関数helloをアタッチします。 kprobeとは Linux カーネル の任意の関数や命令の実行時に動的にフックして処理を挿入できる仕組みです このプログラムが カーネル にロードされると、execve()が実行された際には hello World がtrace_pipe(ユーザー空間から取得可能)に出力されます。 b.trace_print() 最後に、トレースデータを Python が実行されている標準出力へ出力します。 実際に動かしてみます。 環境 カーネル モジュールの検証時と同様 ファイルの作成 sh-5.2$ sudo vim hello.py sh-5.2$ cat hello.py #!/usr/bin/python from bcc import BPF program = r""" int hello(void *ctx){ bpf_trace_printk("Hello World!\\n"); return 0; } """ b = BPF(text=program) syscall = b.get_syscall_fnname("execve") b.attach_kprobe(event=syscall, fn_name="hello") b.trace_print() 実行 #実行権限の付与 h-5.2$ sudo chmod +x hello.py sh-5.2$ sudo ./hello.py ちなみにeBPFは原則特権ユーザーでしか実行できないので、./hello.pyの実行だと以下のようになります sh-5.2$ ./hello.py bpf: Failed to load program: Operation not permitted Traceback (most recent call last): File "/home/ssm-user/./hello.py", line 13, in <module> b.attach_kprobe(event=syscall, fn_name="hello") File "/usr/lib/python3.9/site-packages/bcc/__init__.py", line 841, in attach_kprobe fn = self.load_func(fn_name, BPF.KPROBE) File "/usr/lib/python3.9/site-packages/bcc/__init__.py", line 523, in load_func raise Exception("Need super-user privileges to run") Exception: Need super-user privileges to run 結果 sh-5.2$ sudo ./hello.py b' <...>-46202 [001] ...21 84764.107466: bpf_trace_printk: Hello World!' b' <...>-46203 [001] ...21 84823.020341: bpf_trace_printk: Hello World!' 何も操作してなくても裏でexecve()って呼ばれているんですね。 別の接続を開いてls等のコマンドを打つと、 Hello World !が同時に出力されるのが分かります。 感想 このように特定の システムコール に紐づけて、監視できると分かりました。 まあ、今回の実験では何かが実行されたという情報しかないですが... そしてTetragonへ... このようにeBPFは カーネル で動作できるので、実行環境で監視の範囲を広げられるように感じます。 ただ当然ですが、毎回監視のためのコードを書くのは辛いものがあります。 そこで、はじめに例にあげたTetragonのようなeBPFをベースにしたサービスを利用するんですね。 ここまでくれば、 Kubernetes ではよくある監視の サイドカー コンテナ利用に対して、eBPFという切り口で監視を提供するときに拡張される範囲がわかってくると思います。 メインコンテナに対して同じボリュームを共有し、 localhost で接続可能な サイドカー コンテナとして実行する監視(例:Datadogの サイドカー インジェクション)に加えて、ノードにDaemonSetsとしてデプロイされることで、 カーネル で実行された実行イベントまで監視の目を広げることができますね。 参考: https://tetragon.io/docs/getting-started/execution/ 本記事ではeBPFの紹介なので、Tetragonの詳細の解説は省略します。 おわりに eBPFではよく蜂を見かけますが、これはeBeeという名前です。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sato.yu レビュー: @akutsu.masahiro ( Shodo で執筆されました )
はじめに エンタープライズ 第一本部、2025 Japan AWS Jr. Champions の佐藤悠です。 最近、Node.jsのLambda LayerをTerraformを用いて追加するタイミングがあったので作業ログとして記事を作成します。 はじめに Lambda Layerとは Node.jsのLayerの概要 Node.jsのLayerを作成する 関数を作成し、Layerを用いた動作を確認する TerraformでLayerを作成する 終わりに Lambda Layerとは Lambda レイヤーは、補助的なコードやデータを含む .zip ファイル アーカイブ です。レイヤーには通常、ライブラリの依存関係、カスタムランタイム、または設定ファイルが含まれています。 参考: レイヤーによる Lambda 依存関係の管理 このようにLambdaで動作する主たるコードとは別に補助的な意味合いで使用するものとなります。 私が直面した ユースケース では、共通で利用できるコードとモジュールをパッケージ化する使用方法でした。 公式ドキュメントでは以下の項目にあたるでしょう。 複数の関数間で依存関係を共有するため。 レイヤーを作成したら、それをアカウント内の任意の数の関数に適用できます。レイヤーがない場合、個々のデプロイパッケージに同じ依存関係を含める必要があります。 確かに、毎回使用される依存関係をデプロイのたびにパッケージ化するのはスマートではありません。 今回の内容を図にすると以下のとおりです。 実際にはもっと多くのLambdaが共通部を持っていたので、Layerに切り出し、後からLayerを参照させることでデプロイプロセスが楽になりました。 ※以降の内容では、説明の簡略化のために依存関係のみに着目して作業手順を構成しています。 Node.jsのLayerの概要 続いて、Node.jsをランタイムとするLayerに関する概要です。 関数にレイヤーを追加すると、Lambda はレイヤーのコンテンツをその実行環境の /opt ディレクト リに読み込みます。 参考: 各 Lambda ランタイムのレイヤーパス /optへのパスはLambda側で通してくれるので、作成側としては適切なパスに配置したフォルダをアップロードする必要があります。 パスの指定は以下のようになります。 ランタイム パス Node.js nodejs/node_modules Node.js 18 nodejs/node18/node_modules (NODE_PATH) Node.js 20 nodejs/node20/node_modules (NODE_PATH) Node.js 22 nodejs/node22/node_modules (NODE_PATH) Node.jsのLayerを作成する 初めの概要に示したようにLambda Layerは.zip アーカイブ なのでパッケージングを行う必要があります。 まずは任意の ディレクト リで以下のコマンドを実行します。 mkdir -p nodejs npm install --prefix nodejs lodash 今回は後段でlodashを使用するサンプルコードを動作させたいと思うので以上のようにしています。 ここでパスを確認すると以下のようになっており、Layer作成の際のフォルダ構成を守れています。 ※layer_demoフォルダで作業しています layer_demo\nodejs\node_modules\lodash 次に以下のコマンドで作成したフォルダと配下にインストールした内容をzip化します。 zip -r layer.zip nodejs/ 最後に AWS CLI コマンドを実行してLayerを AWS 上に作成します。 このコマンドではLayerを「my-layer」という名前で作成し、ランタイムにNode.js22.xを指定しています。 先ほど作成したZIPファイルへのパスは適切なものに変更してください。 aws lambda publish-layer-version --layer-name my-layer --zip-file fileb://path_to/layer.zip --compatible-runtimes nodejs22.x 参考: レイヤーコンテンツのパッケージング 作成できたことを AWS マネジメントコンソールから確認します。 以下のように「my-layer」が作成できました。 関数を作成し、Layerを用いた動作を確認する 次にサンプルのLambda関数を作成してLayerの動作を確認してみます。 サンプルのアプリケーションとして、 sample-apps/layer-nodejs が AWS から提供されているので、この中の index.mjs を利用します。 import _ from "lodash" export const handler = async (event) => { var users = [ { 'user': 'Carlos', 'active': true }, { 'user': 'Gil-dong', 'active': false }, { 'user': 'Pat', 'active': false } ]; let out = _.findLastIndex(users, function(o) { return o.user == 'Pat'; }); const response = { statusCode: 200, body: JSON.stringify(out + ", " + users[out].user), }; return response; }; lodashのfindLastIndexメソッドを使い、users配列の中からuserがPatである最後の要素のインデックスを取得しています。 今回の配列では、Patは最後の要素なので、outは2になります。 関数は AWS マネジメントコンソール上から直接デプロイしました。 Layerの追加前なのでテストを実施すると、以下のようなエラーが発生します。 " errorMessage ": " Cannot find package 'lodash' imported from / var / task / index . mjs " では、先ほど作成したLayerを追加します。 以下の画像のレイヤーの追加を押下します。 続いてARNを指定することで先ほどの「my-layer」を追加します。 ※ARNはLayerの画面でコピーして取得しました。 追加を押下して、「関数が正常に更新されました。」の表示を確認するとLayerの追加は完了です。 ちなみに AWS CLI では、以下のコマンドの関数名とLayerのARNを指定することで追加可能です。 aws lambda update-function-configuration --function-name my-function --cli-binary-format raw-in-base64-out --layers "arn:aws:lambda:us-east-1:123456789012:layer:my-layer:1" 参考: Node.js Lambda 関数のレイヤーを操作する Layerが追加できたことが確認できたので、再度関数のテストを実行します。 正常に動作することが確認でき、出力も期待通りのものが得られました。 TerraformでLayerを作成する ここまでは AWS マネジメントコンソールや AWS CLI を使用していましたが、Terraformでこれを実現する方法も記載します。 IaCでリソースを管理する際の参考になればと思います。 まず、基本的なリソース定義は以下のとおりです。 ※公式ドキュメントのsampleです resource "aws_lambda_layer_version" "example" { filename = "lambda_layer_payload.zip" layer_name = "lambda_layer_name" compatible_runtimes = ["nodejs20.x"] } 参考: Resource: aws_lambda_layer_version 同じプロジェクトの先輩が、Layerに変更があるたびにnpm installやzipコマンドを毎回実行することなくterraform applyできるコードを書いていたので紹介します。 locals { package_json = "$ { path.module } /nodejs/package.json" layer_dir = "$ { path.module } /nodejs" } resource "terraform_data" "prepare_layer" { triggers_replace = { package_json_sha = filesha256 (local.package_json) } provisioner "local-exec" { interpreter = [ "bash" , "-c" ] command = <<-EOT rm -rf "$ { local.layer_dir } /node_modules" npm install --prefix nodejs EOT } } data "archive_file" "layer_zip" { type = "zip" source_dir = local.layer_dir output_path = "$ { path.module } /layer.zip" depends_on = [ terraform_data.prepare_layer ] } resource "aws_lambda_layer_version" "demo_layer" { filename = data.archive_file.layer_zip.output_path layer_name = "layer-demo" compatible_runtimes = [ "nodejs22.x" ] } ポイントは terraform_data の使い方です。 package. json に変更があれば、それをトリガーにしてモジュールをインストールするように構成されています。 このおかげで、terraform applyをする前に手動でコマンドを実行するプロセスがなくなりますね。 毎回、npm installを実行しようとしていたので悔しかったです。(もう忘れない...) 終わりに Layer作成の際の一助になれば、幸いです。 ここまでお読みいただきありがとうございました。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sato.yu レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
こんにちは。グループ経営ソリューション事業部の圷(あくつ)と申します。 2021年に「技術職」として入社し、一貫して会計パッケージの導入チームで仕事をしています。 具体的には、「 Tagetik 」という 経営管理 ソフトウェアの導入プロジェクトに入っています。 皆さんは「ITエンジニア」と聞くと、パソコンに向かってコードを書いている人を思い浮かべるかもしれません。 実はコードを書かない システムエンジニア (SE)も多く存在します。私自身も日ごろの業務でプログラミングをする時間は非常に少ないです。(ゼロではないですが。) 本記事では、主に就活生の皆さん向けに、私自身の経験から「プログラミングを行わない システムエンジニア 」の仕事内容とその魅力についてお伝えします。 "パッケージ"とは? 先ほど、「会計パッケージの導入チーム」で仕事をしていると書きましたが、そもそも「パッケージってなんだよ」と思われたことでしょう。IT業界では「パッケージ」という言葉がよく使われます。パッケージと呼んでいるのは「 パッケージソフト ウェア」の略で、特定の業務や目的に合わせて事前に開発され、複数の機能があらかじめ備わっているソフトウェアのことを指します。 電通 総研の主要製品である「 POSITIVE 」や「 STRAVIS 」もパッケージの一種です。 この中で重要なのが 「事前に開発され」「あらかじめ備わっている」 という部分です。すなわち、機能の大部分は導入の際に自分で実装する必要がないということです。企業の業務の中である程度共 通化 できる部分を前もって開発しておくことで、顧客に導入する際のコスト・期間を大幅に短縮することができます。 パッケージの"導入"とは? ここまでパッケージと呼ばれる製品について説明してきましたが、今度は「導入」に関わる部分を説明します。 パッケージ製品においては、機能の大部分が共 通化 されています。 この共 通化 された部分を開発しているのは主に「開発チーム」と呼ばれるような人たちです。彼ら彼女らはプログラミングが仕事のコアとなるので、一般的な「ITエンジニア」に近いイメージの仕事をしています。 開発チームが実装した機能を、顧客に実際に使ってもらう準備をするのが「導入チーム」の仕事 です。「すでに機能が開発されているのに準備がいるの?」と思われる方がいるかもしれないですが、実際には多くの準備作業が必要になります。 例えば、GoogleFormのようなアンケートツールを使う場合、アンケート作成者は「質問文」「回答タイプ」「必須回答」といった設定をする必要があります。パッケージ製品の場合も、これと同様に顧客の用途に合った事前設定が必要です。GoogleFormの場合は設定がそこまで複雑ではないためユーザー側で設定を完了できますが、顧客業務で使用するソフトウェアは設定が複雑かつ影響範囲が非常に広大なため、専門的な知識を持つ導入チームによるサポートを実施します。 そして、多くの場合導入チームによるサポート作業ではプログラミングは行わず、設定作業が主となります。そのため、 製品に関する専門的な知識は持つが、普段の仕事でプログラミングをあまりしないエンジニア が存在するのです。(ようやくプログラミングを行わない話に戻ってこられた!!) ここまで、 電通 総研ではパッケージ製品に関わる仕事が非常に多いこと、そしてパッケージ製品は開発チームと導入チームの役割分担にて顧客に届けられていることを説明してきました。ここからは私の例を通して業務の実例を提示したいと思います。 パッケージ導入の流れとSEの役割 パッケージ導入の流れは、一般的な システム開発 の流れとほぼ同じです。 下記は システム開発 でよく取られる「 ウォーターフォール 」という手法を単 純化 したものです。 要件定義から システム開発 をスタートし、 下流 の工程に順次進んでいきます。 他の手法として アジャイル 開発を選択することもありますが、下記のサイクルを圧縮して繰り返す手法と思ってもらって大丈夫です。 そのため、ここからは上記の各工程でプログラミングを行わないSEがどのようなことを行っているかを説明します。 要件定義 このフェーズでは、顧客の要望を確認し、実装する機能を決定します。ここでいう「機能」は実際に業務で使用する画面や各メニューのレベルです。 Tagetikの場合は、 Excel に似た帳票画面を作成できるため、どのような帳票が必要かを顧客と一緒に検討します。帳票画面は設定のみで素早く(簡易的なものであれば数時間程度)で作成できるため、実際の帳票画面を見ながら検討を進めることもあります。 実物を見ながら検討を進められるのは、汎用的な機能が事前に開発されているパッケージ製品を導入するメリットです。 また、帳票画面に出すデータを作成するための数値加工の処理や、各種システムとのデータ連携有無もこの段階で確認します。 設計 このフェーズでは、実際に使用する機能の設計書を作成します。 プログラミングを行わないパッケージ導入でも、設定内容は複雑かつ、各機能で影響を与え合うためドキュメント化は重要な作業です。 また、設計時に意識しないといけない大きなテーマとして「パフォーマンス(処理速度)」があります。皆さんもWeb画面やシステムが遅くてイライラした経験があるかと思いますが、パッケージを導入する際も各機能の処理速度はユーザーがかなり気にするポイントです。一からプログラミングを行って システム開発 を行う場合と異なり、 パッケージの場合は導入時にコードを最適化する余地が限られているため、各パッケージのベストプ ラク ティスに沿った設計を行う必要があります。 そのため、パッケージ導入に関わる時Eはパッケージの知識をつけておくことが非常に重要です。 データ連携や加工が発生する場合はこのフェーズで詳細を決定します。特にデータ連携の場合は他システムとのかかわりが出てくるため、パッケージのみにとどまらない システム開発 全般への汎用的な知識も必要となります。 開発(設定作業) プログラミング言語 でコードを書く代わりに、ツール上の設定画面を操作してシステムの機能を構築します。設定は複雑かつそれぞれが影響し合っている場合が多いため、パッケージへの習熟度合いが鍵になります。また、 設定する際に アルゴリズム を考慮する必要がある場合があり、プログラミング的な脳の使い方も活きてきます。 テスト:設定したシステムが想定どおり動くかを確認します。テストケースの設計の際は、 システム開発 で使用されている手法を採用することが多いです。そのため、テストに関する知識はパッケージ導入でも必要となります。 運用サポート:完成したシステムをユーザーで使用する際のサポートを行います。マニュアル作成・ユーザート レーニン グ・問い合わせ対応等と行う業務は多岐にわたりますが、どれもシステムの仕様を把握していることが重要になります。そのため、それまでの導入作業で知識をつけてきているエンジニアが引き続き重宝されます。 パッケージ導入をする中でついた技術的スキル 前の章で説明してきたように、パッケージ導入をする中でも多くの技術的スキルを使用しています。 私が4年半働いてきて多少はついてきたのでは、と思う技術的スキルは下記のとおりです。 パッケージの製品知識 私の場合はTagetikという 経営管理 ソフトウェアを扱っていますが、その製品知識をつけることができました。各パッケージの知識はそのパッケージを扱うプロジェクトに入らないとなかなか身に着けることができないので、その知識がそのまま希少価値になります。(メジャーなパッケージであれば活躍の機会は多くなりますが、ライバルも多くなります。マイナーなパッケージであるとその逆です。) クラウド の知識( AWS ) 私がこれまで従事してきた案件は AWS で構築した環境にソフトウェアをインストールするものでした。そのため、基盤である AWS に関する知識もつけることができました。もちろん AWS の専門家ほどではないですが、一定程度のスキルはついてきたと感じています。 データベースや SQL に関する知識 Tagetikでは、事前定義されたテーブルとプロジェクト内で設定していくカスタムテーブルの領域があります。このカスタムテーブルの設計を行うのは導入チームのエンジニアなので、開発チームでなくてもデータベース設計や SQL に関する知識が求められます。私の場合は、自分で勉強したり開発経験が豊富なメンバーに教えてもらったりしながら必要な知識を習得していきました。 最後に 「プログラミングを行わない システムエンジニア 」の仕事内容について、私の実体験を交えて紹介してきました。仕事の中でコードを書かなくても「エンジニア」として自分の知識を使って仕事をしていることが伝わっていれば幸いです。 個人的にも、知識を習得するのは好きなので、今の仕事はちょっと向いているのかなと思っています。自分の知識を使って仕事をしていきたい!というみなさんと一緒に働けたらとても嬉しいです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @akutsu.masahiro レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
こんにちは! グループ経営ソリューション事業部の 米久 保です。 みなさん、 MCP (Model Context Protocol)を活用してAIエージェントを強化していますか? この記事では、Postgres MCP Pro という MCP サーバーを使うとデータベースアクセスの開発効率が向上したよ、という話をします。 Postgres MCP Proとは 構成 Dockerコンテナの作成 MCPサーバーの利用設定 Domaによるデータアクセス処理の実装 所感 Postgres MCP Proの有用性 Domaの生成AI親和性 動作検証環境 Postgres MCP Proとは Postgres MCP Pro は、米国のスタートアップ企業 Crystal DBA 社が開発する オープンソース (ライセンスはMIT)の MCP サーバーです。提供する主要機能は以下のとおりです。 データベースヘルス :コネクション使用率やバッファキャッシュなどデータベースの健全性を分析する インデックスチューニング :ワークロードに最適なインデックスを提案する クエリプラン :クエリプランを確認し、パフォーマンスを最適化する スキーマ インテリジェンス :データベース スキーマ の理解に基づいた SQL 生成を支援する 安全な SQL の実行 :読み取り専用モード、安全な SQL 解析などアクセス制御を提供する Claude Code や GitHub Copilot などのコーディングAIエージェントに Postgres MCP Pro が提供するツールを利用させることで、 PostgreSQL データベースサーバーを用いるアプリケーションの開発や運用を効率化することが可能です。 構成 アプリケーション開発業務において Postgres MCP Pro を活用することを目的とし、以下の構成で検証を行いました。 IDE には IntelliJ IDEA を使用し、 GitHub Copilot の プラグイン をインストールする 開発用のDB( PostgreSQL )、 Postgres MCP Pro をそれぞれ Docker コンテナとしてローカルPC上に起動する GitHub Copilot の LLM を利用する(モデルは Claude Sonnet 4) 実際の検証は筆者が担当するプロダクトのコードを利用しましたが、本稿の執筆にあたってはサンプルのDBとコードで同様の検証を行った結果をもとにしています。 Dockerコンテナの作成 docker-compose.yml を以下に示します。サンプルデータには、 PostgreSQL Sample Database を利用し、初期化 スクリプト で投入するようにしています。Postgres MCP Pro は PostgreSQL サーバーに依存するため、ヘルスチェック後に立ち上げます。コンテナ上の開発DBは最悪壊れてもダメージはないので、 --access-mode=unrestricted を指定し Postgres MCP Pro は無制限モードで立ち上げています。 services : postgres : container_name : postgres-dvdrental image : postgres:17 environment : - POSTGRES_USER=postgres - POSTGRES_PASSWORD=<your_password> - POSTGRES_DB=postgres ports : - "5432:5432" volumes : - postgres_data:/var/lib/postgresql/data - ./db/dvdrental.tar:/docker-entrypoint-initdb.d/dvdrental.tar:ro - ./db/init-dvdrental.sh:/docker-entrypoint-initdb.d/init-dvdrental.sh:ro healthcheck : test : [ "CMD-SHELL" , "pg_isready -U postgres" ] interval : 10s timeout : 5s retries : 5 networks : - sample_network postgres-mcp-pro : image : crystaldba/postgres-mcp container_name : postgres-mcp-pro restart : unless-stopped environment : - DATABASE_URI=postgresql://postgres:<your_password>@postgres:5432/dvdrental ports : - "8000:8000" command : [ "--access-mode=unrestricted" , "--transport=sse" ] depends_on : postgres : condition : service_healthy networks : - sample_network volumes : postgres_data : networks : sample_network : driver : bridge docker-compose up -d でコンテナを起動すれば、準備完了です。 MCP サーバーの利用設定 次に、コーディングAIエージェントが MCP サーバーを利用できるように設定します。設定方法はAIツールにより異なりますが、 IntelliJ の場合は mcp.json に以下のように記述します。 { " servers ": { " postgresql ": { " type ": " sse ", " url ": " http://localhost:8000/sse " } } } 設定を終えたら、動作確認してみましょう。たとえば、「データベースにテーブルは何個存在しますか?」とチャットで尋ねると下図のような応答が得られました。 list_schemas や list_objects というツールを利用して、 PostgreSQL のDB情報を取得できていることがわかります。 Doma によるデータアクセス処理の実装 Doma は、 Java でデータアクセス処理を実装するための オープンソース の フレームワーク です。 電通 総研は、 IntelliJ の プラグイン を開発して寄贈するなど、 Doma の オープンソース 開発を積極的に支援しています。 電通総研、Javaフレームワーク「Doma(ドマ)」の利用支援ツールを開発 今回は、サンプルDBからデータを取得する リポジトリ インターフェースを以下のように定義し、AIエージェントにデータアクセス層のコードと SQL をすべて実装させてみました。Postgres MCP Proの利用により成果物の精度が向上することを確認するのが目的です。 package com.example.rentaldemo; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; public interface CustomerActorAnalysisRepository { /** * 指定顧客の期間内レンタル作品から最頻出俳優を特定. * * @param customerId 顧客ID * @param startDate 分析開始日時 * @param endDate 分析終了日時 * @return 最頻出俳優情報(同率1位の場合は任意の1件) */ Optional<TopActorAnalysis> findTopActorsByCustomerRentals( Integer customerId, LocalDateTime startDate, LocalDateTime endDate ); /** * 指定俳優の出演作品のうち顧客が未レンタルの作品を取得. * * @param customerId 顧客ID * @param actorId 俳優ID * @param limit 取得件数上限 * @return 未視聴作品リスト */ List<UnwatchedFilmRecommendation> findUnwatchedFilmsByActor( Integer customerId, Integer actorId, Integer limit ); } カスタム指示( copilot-instructions.md )には、以下のようにデータアクセス処理の実装ルールを定めておきます。 # データアクセス処理の実装 + DOMAを使ってDAOを実装すること + DBからフェッチしたデータはEntityオブジェクトにマッピングしてDAOから返却する + RepositoryはEntityをDTOに変換して返却する + DOMAの警告やエラーが発生している場合は適切に修正すること + 2-way SQLが、実際にDBで動作することを確認すること AIエージェントとのチャットのやり取りは長くなるため、冒頭部分のみ示します。 DBのテーブル構造を把握したうえで、適切な SQL を組み立てようとしていることがわかります。 生成されたDAOのインターフェースは以下です(インターフェースの実装クラスは、 Doma が自動生成してくれます)。 package com.example.rentaldemo.dao; import com.example.rentaldemo.entity.CustomerActorAnalysisEntity; import com.example.rentaldemo.entity.UnwatchedFilmEntity; import org.seasar.doma.Dao; import org.seasar.doma.Select; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @Dao public interface CustomerActorAnalysisDao { /** * 指定顧客の期間内レンタル作品から最頻出俳優を特定 * * @param customerId 顧客ID * @param startDate 分析開始日時 * @param endDate 分析終了日時 * @return 最頻出俳優情報(同率1位の場合は任意の1件) */ @Select Optional<CustomerActorAnalysisEntity> findTopActorByCustomerRentals( Integer customerId, LocalDateTime startDate, LocalDateTime endDate ); /** * 指定俳優の出演作品のうち顧客が未レンタルの作品を取得 * * @param customerId 顧客ID * @param actorId 俳優ID * @param limit 取得件数上限 * @return 未視聴作品リスト */ @Select List<UnwatchedFilmEntity> findUnwatchedFilmsByActor( Integer customerId, Integer actorId, Integer limit ); } 一つ目のメソッド findTopActorByCustomerRentals に対応する SQL テンプレートは以下のように生成されました。 SELECT a.actor_id, a.first_name, a.last_name, COUNT (*) as appearance_count, c.customer_id, CONCAT (c.first_name, ' ' , c.last_name) as customer_name FROM rental r JOIN inventory i ON r.inventory_id = i.inventory_id JOIN film f ON i.film_id = f.film_id JOIN film_actor fa ON f.film_id = fa.film_id JOIN actor a ON fa.actor_id = a.actor_id JOIN customer c ON r.customer_id = c.customer_id WHERE r.customer_id = /* customerId */ 1 AND r.rental_date >= /* startDate */ ' 2005-01-01 00:00:00 ' AND r.rental_date <= /* endDate */ ' 2005-12-31 23:59:59 ' GROUP BY a.actor_id, a.first_name, a.last_name, c.customer_id, c.first_name, c.last_name ORDER BY appearance_count DESC LIMIT 1 Doma では、Daoのメソッドパラメータと、 SQL テンプレート中のバインド変数とが対応づいている必要があります。たとえば、メソッドパラメータ customerId には、 SQL テンプレートの /* customerId */ が対応します。 所感 Postgres MCP Proの有用性 今回の検証では、コンテキストにDB設計に関する情報は一切含めていません。にもかかわらず、 MCP サーバーを経由してDBのメタ情報を取得して利用することで、AIエージェントがデータモデルを把握して SQL を正しく組み立てられるようになりました。サンプルDBがシンプルなことも一因でしょうが、コンテキストに与える情報を工夫すれば、実際の開発現場で取り扱う大きなデータモデルにおいても精度向上が見込めるでしょう。 また、 MCP サーバーによって、生成した SQL を実行してフィードバックを得ることが可能となります。それにより、AIエージェントが実行可能な SQL を生成することを担保することができます。Postgres MCP Pro はパフォーマンスチューニングのツールも提供するため、 SQL の性能面においても効果が期待されます。 Doma の生成AI親和性 AIエージェントが MCP サーバー経由で SQL の実行確認ができるのは、 Doma の two-way SQL の仕組みのおかげです。先に示した SQL テンプレートでは、バインド変数の埋め込みは SQL コメントを用いて記述されています。このように、テンプレート上の諸々の操作は SQL コメントを利用するため、 Doma の SQL テンプレートは基本的に そのまま SQL として実行可能 です。 また、 Doma は コンパイル 時に アノテーション 処理を使用して ソースコード の検証と生成を行います。前述の「Daoのメソッドパラメータと、 SQL テンプレート中のバインド変数が対応づいている」という条件も コンパイル 時にチェックされ、満たしていなければエラーが出力されます。AIエージェントは、環境と相互作用し、 得られたフィードバックをもとに誤りを訂正しながらゴールへと近づいていく ものです。 Doma の コンパイル 時チェックは、AIエージェントの利用において非常に有益であると感じました。 動作検証環境 今回の検証に利用した環境は以下のとおりです。 OS Windows 11 Pro 開発環境 Java 17.0.11 ( Amazon Coretto) IntelliJ IDEA 2025.1.5.1 (Community Edition) (Plugin) GitHub Copilot 1.5.55 (Plugin) Doma Tools 2.3.0 Spring Boot 3.3.4 Doma 3.0.0 仮想環境 Docker Desktop 4.37.1 PostgreSQL 17.6 Postgres MCP Pro 0.3.0 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 執筆: @tyonekubo レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
はじめまして。クロスイノベーション本部、新卒1年目の大岡叡です。 現在、技術職に配属された同期6名とともに7月から9月の3か月間にわたるシステム開発研修に取り組んでいます。研修では、運営側が定めた必須機能に加えてチームで考えた追加機能を自由に実装することが認められています。 私のチームではある追加機能を実装することを決定し、その中でテキストの感情分析を取り入れることにしました。 開発したシステムは AWS ECS on Fargate にデプロイすることが決まっており、また私が学生時代からAWSの資格勉強をしていたことから、テキストの感情分析には Amazon Comprehend を利用することにしました。それにあたり、Amazon Comprehendを調査し実際にどのように使うのか検証しました。 本記事では、Amazon Comprehendを調べてみて・触ってみて学んだことをまとめています。 同じように初めてAmazon Comprehendを触る方のヒントになればと思います。 Amazon Comprehendの概要 概要 感情分析について DetectSentiment API BatchDetectSentiment API 感情分析APIの料金 具体的な料金例 シーケンス図 入力したテキストを感情分析させる簡単な検証 単一テキスト分析 複数テキスト分析 具体的なユースケースを想定した検証 まとめ 参考文献 Amazon Comprehendの概要 概要 Amazon ComprehendについてAWS公式サイトでは以下のように説明しています。要するに、Amazon Comprehendはテキストを様々な観点から分析してくれるツールです。 Amazon Comprehend は、機械学習 (ML) を使用してテキストから洞察を見つける自然言語処理 (NLP) サービスです。Amazon Comprehend は、Custom Entity Recognition、カスタム分類、キーフレーズ抽出、感情分析、エンティティ認識などの API を使用することにより、お使いのアプリケーションに NLP を簡単に統合できます。Amazon Comprehend API をお使いのアプリケーションに読み込むだけで、ソースとなるドキュメントやテキストの場所がわかります。API は、エンティティ、キーフレーズ、感情、言語を、アプリケーションで使用できる JSON 形式で出力します。 ( AWS公式サイト より引用) 感情分析について Amazon Comprehend の感情分析は、テキストがどのような感情を持っているのかを分類して返してくれる機能です。判定される感情は主に以下の4種類です。 POSITIVE (ポジティブな内容) NEGATIVE (ネガティブな内容) NEUTRAL (中立的な内容) MIXED (ポジティブ・ネガティブが混在) APIにテキストを送信すると単に感情ラベルを返すだけでなく、それぞれの感情にどれくらいの確信度があるかをスコア(0.0~1.0)で返してくれます。 利用できるAPIは "DetectSentiment"、"BatchDetectSentiment"、"StartSentimentDetectionJob"の3種類です。本記事では、"DetectSentiment"と"BatchDetectSentiment"を紹介します。("StartSentimentDetectionJob"はS3上のテキストデータを対象に分析を行うAPIですが、研修で私のチームが構築しているシステムではテキストデータをS3に配置しないため説明を割愛いたします) DetectSentiment API 1件のテキストを対象に感情を判定するAPIです。 リクエスト例 { " Text ": " 今日はとても良い気分です ", " LanguageCode ": " ja " } レスポンス例 { " Sentiment ": " POSITIVE ", " SentimentScore ": { " Positive ": 0.999679684638977 , " Negative ": 8.19311972009018e-05 , " Neutral ": 0.0002277959865750745 , " Mixed ": 1.0496267350390553e-05 } } BatchDetectSentiment API 複数件( 最大25件 )のテキストをまとめて感情判定できるAPIです。 リクエスト例 { " TextList ": [ " 今日は楽しい ", " 明日は不安だ " ] , " LanguageCode ": " ja " } レスポンス例 { " ResultList ": [ { " Index ": 0 , " Sentiment ": " POSITIVE ", " SentimentScore ": { " Positive ": 0.9967367053031921 , " Negative ": 7.083657692419365e-05 , " Neutral ": 0.0030982368625700474 , " Mixed ": 9.416837565368041e-05 } } , { " Index ": 1 , " Sentiment ": " NEGATIVE ", " SentimentScore ": { " Positive ": 0.0007383294287137687 , " Negative ": 0.9037773013114929 , " Neutral ": 0.09547645598649979 , " Mixed ": 7.853302122384775e-06 } } ] , " ErrorList ": [] } 感情分析APIの料金 Amazon Comprehend の料金は、処理するテキスト量に応じた従量課金になっています。東京リージョンでの感情分析に関する料金は以下のとおりです(2025年9月2日時点)。 0.0001 USD / 1 単位 (100文字ごと) 日本語テキストを含む多言語に対応 各リクエストにつき3単位 (300 文字)の最低料金が発生します。 具体的な料金例 1件あたりの料金 テキスト長 課金単位数 料金 100文字 3単位(最低料金適用) 0.0003 USD 300文字 3単位 0.0003 USD 500文字 5単位 0.0005 USD 1,000文字 10単位 0.001 USD ※ 300文字以下のテキストは最低料金(3単位 = 0.0003 USD)が適用されます。 月間利用量ごとの料金目安(500文字/件の場合) 件数 料金 1,000件/月 0.5 USD(約75円) 10,000件/月 5 USD(約750円) 100,000件/月 50 USD(約7,500円) ※ 1 USD = 150円で換算 シーケンス図 感情分析を検証するために、以下のシーケンス図に基づいたシステムを実装しました。私のチームで考えた追加機能では複数のテキストをまとめて分析するため、Amazon ComprehendのBatchDetectSentiment APIを使用しました。また、研修でNext.js と Spring Bootを用いた開発を行っているため、今回も同様の技術スタックを採用しています。 次のセクションから、このシーケンス図に基づいたシステムを用いて行った検証について説明します。今回は検証であるため、Next.jsとSpring Bootはlocalhostでホストしました。 入力したテキストを感情分析させる簡単な検証 このセクションでは入力したテキストを感情分析させる簡単な検証について説明します。以下のような画面で検証を行いました。 テキストボックスにテキストを入れ、分析するボタンを押下すると分析結果が表示されます。 単一テキスト分析 まず、1つのテキストを分析するケースを試してみました。 「今日は疲れたが、色々楽しいことがあった一日だった。」という文は、「疲れた」という NEGATIVE な要素と「楽しい」という POSITIVE な要素が混ざっているため、MIXED と判定されました。期待通りの結果となったため、正しく動作していると考えられます。 複数テキスト分析 続いて、複数のテキストを分析するケースを試してみました。 単一テキストと同様に問題なく分析してくれました。 補足 「かんぱーい!」はポジティブに判定されるのでは?と思い、Amazon Comprehend のレスポンスを確認したところ、POSITIVE 約30%、NEGATIVE 約7%、NEUTRAL約62%という結果でした。 確かに「かんぱーい!」と発していても、もし気が進まない飲み会だったらネガティブな感情も含まれますよね。 C:\Users\torut>aws comprehend detect-sentiment --region ap-northeast-1 --language-code "ja" --text "かんぱーい!" { "Sentiment": "NEUTRAL", "SentimentScore": { "Positive": 0.3049440085887909, "Negative": 0.07437098026275635, "Neutral": 0.6194847822189331, "Mixed": 0.0012001828290522099 } } 具体的なユースケースを想定した検証 最後に、具体的なユースケースを想定し、アンケートの回答内容の感情分析を一括で行う検証を行いました。 結果は以下のようになりました。感情分析の結果も妥当であると感じました。 まとめ 今回、Amazon Comprehend を調査・検証してみて、特に印象に残ったのは 料金の安さ と バッチ処理の上限件数 です。 料金については、100文字単位で課金される仕組みですが、実際に計算してみると小規模な分析であればほとんどコストを気にせずに利用できることが分かりました。 一方で、BatchDetectSentiment API のリクエスト上限が1回あたり最大25件である点には注意が必要だと感じました。例えば 100 件を分析する場合、4 回に分けてリクエストを送る必要があります。実運用を考える際には、この制約を踏まえて処理設計を行う必要があると学びました。 そして、検証を通じて、Amazon Comprehendを利用するイメージがついたので、本検証を基に研修で開発しているシステムへの導入を進めていきたいと思います。 参考文献 https://aws.amazon.com/jp/comprehend/features/ https://aws.amazon.com/jp/comprehend/pricing/ https://docs.aws.amazon.com/comprehend/latest/dg/how-sentiment.html https://docs.aws.amazon.com/comprehend/latest/APIReference/API_DetectSentiment.html https://docs.aws.amazon.com/comprehend/latest/APIReference/API_BatchDetectSentiment.html https://docs.aws.amazon.com/comprehend/latest/APIReference/API_StartSentimentDetectionJob.html 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @ooka.toru レビュー: @miyazawa.hibiki ( Shodo で執筆されました )
こんにちは。クロス イノベーション 本部 クラウド イノベーション センターの柴田です。本記事では Claude Code の画面に今月の利用料を表示する方法をご紹介します。 動機 利用する機能・ツール 実行環境 ステータスライン ccusage 設定方法 1. ステータスラインの表示内容を出力するプログラムを実装する 2. Claude Code の設定ファイルを更新する 実行結果 おわりに 参考資料 動機 私は Claude Code を Amazon Bedrock 経由で利用しています。 Amazon Bedrock は従量課金なのでうっかり Claude Code を使いすぎて利用料が高額にならないように気をつけなければなりません。そこでその対策として Claude Code の画面に今月の利用料を表示する ことにしました。 利用する機能・ツール 実行環境 本記事の実行環境は以下のとおりです。 Ubuntu 24.04 @anthropic-ai/claude-code@1.0.98 ccusage@16.2.0 ステータスライン Claude Code には画面下部にユーザー独自のステータスラインを表示する機能があります。今回はこの機能を使って Claude Code の画面に利用料情報を表示します。 ccusage ccusage は Claude Code の使用状況を分析するための CLI ツールです。今回はこの ccusage を使って Claude Code の今月の利用料を計算します。 1 なお ccusage にはステータスライン用の内容を出力する ccusage statusline コマンドがありますが、今月の利用料の表示をサポートしていないため今回は利用しません。 設定方法 1. ステータスラインの表示内容を出力するプログラムを実装する ステータスラインの表示内容を出力するプログラムを実装します。今回は以下の シェルスクリプト を ~/.claude/statusline.sh に保存します。 #!/bin/bash echo "💰 \$$(npx ccusage@16.2.0 monthly --json --offline --order desc | jq -r '.monthly[0].totalCost * 100 | round / 100') monthly" この シェルスクリプト では以下の処理を行っています。 ccusage monthly で月次の利用料を計算します。頻繁に実行されることを考慮してモデルの価格データは API ではなくキャッシュから取得します。 jq で今月の利用料のみを取得します。また利用料を小数第二位で四捨五入します。 結果を標準出力へ出力します。 chmod で先ほどの シェルスクリプト に実行権限を付与します。 chmod +x ~/.claude/statusline.sh 2. Claude Code の設定ファイルを更新する Claude Code の設定ファイル(ユーザー設定なら ~/.claude/settings.json )の .statusLine.command に先ほど実装したプログラムのパスを指定します。 { " statusLine ": { " type ": " command ", " command ": " ~/.claude/statusline.sh " } } 設定は以上で完了です。 実行結果 実際に Claude Code の画面に今月の利用料が表示されるか確認してみましょう。適当なプロジェクトで Claude Code を起動します。すると画面の下部に今月の利用料が表示されていることを確認できます。 おわりに 本記事では Claude Code の画面に今月の利用料を表示する方法をご紹介しました。ステータスラインの表示内容はカスタマイズが可能です。利用料以外にも表示したい情報があれば追加してみてください。最後までお読みいただき、ありがとうございました。 参考資料 Status line configuration - Anthropic https://github.com/ryoppippi/ccusage 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @shibata.takao レビュー: @takami.yusuke ( Shodo で執筆されました ) ccusage は Claude Code が出力した トーク ン数やコスト情報から推定利用料を算出します。 Amazon Bedrock の実際の利用料のデータを取得しているわけではありません。本記事の方法で表示される利用料は目安として捉えてください。 ccusage のコスト計算の仕組みは Cost Modes | ccusage をご覧ください。 ↩
はじめに bunのインストール 作業環境の作成 Prettierのインストール Remarkのインストール パーザのセットアップ RemarkとPrettierの整合性を取る Remarkの一部としてPrettierを動かす Linterのセットアップ ドキュメントのリンク切れを検証する Linter用の設定ファイルを外部化する まとめ はじめに みなさんこんにちは、XI本部エンジニアリングオフィスの佐藤太一です。 Amazon のKiroが話題になったあたりから、AIに Markdown で仕様書を書いてもらうことで成果物の品質や作業品質を高める取り組みが注目されていますね。 Amazon のKiroだけでなく、 GitHub の Spec Kit 、 Claude Code Spec Workflow や Claude Code Spec など様々な活動が公開されています。 このエントリでは、そういった活動の中で少し見過ごされがちな Markdown ファイルそのものの品質を底上げするための取り組みを紹介します。 具体的には、RemarkとPrettierを組み合わせて Markdown ファイルが所定のルールに沿った記述がされているか自動的に保証する方法を説明します。 bunのインストール 今回利用するツールは JavaScript やTypeScriptで記述されています。 これらのツールは、継続的に繰り返し実行するため、少しでも高速に動作することが望ましいので、今回はbunを使って実行します。 Windows 環境であれば以下のコマンドでインストールできます。 powershell -c "irm bun.sh/install.ps1 | iex" Linux や Mac 環境なら以下のコマンドでインストールできます。 curl -fsSL https://bun.sh/install | bash bunは高速に動作するうえに特別な設定をせずにTypeScriptを直接実行できるので、私は日常的な スクリプト もTypeScriptで書いています。 作業環境の作成 今回は筆者の作業環境が Windows なので、それを前提に環境構築を行います。 プラットフォーム依存性のある話は全体としてほぼ存在しませんので、 Linux や Mac を使っている方は適宜読み替えてください。 作業用の ディレクト リとして、 C:/dev/md-tutorial に新しい ディレクト リを作成しました。 今後は、この ディレクト リ内で作業しているものと考えてください。 この ディレクト リに、 bunfig.toml というファイルを以下の内容で作成します。 [install] exact = true これによって、bunでインストールするツールのバージョンが常に固定されたものになります。 Prettierのインストール Prettierは、htmlや JavaScript 、TypeScriptなど最近のフロントエンド開発で使われているテキストファイルのフォーマットを自動的に行う 意見の強い フォーマッタです。 私自身は、全ての ソースコード はフォーマットをすべきであると考えています。 ただし、一貫性さえあればフォーマットのルールについてはあまりこだわりがないので、Prettierを常用しています。 以下のコマンドを実行して、Prettierをbunでインストールしましょう。 bun add prettier 以下のように package.json が作成されます。 { " dependencies ": { " prettier ": " 3.6.2 " } } これにbunからPrettierを実行するためのコマンドを追加します。 { " dependencies ": { " prettier ": " 3.6.2 " } , " scripts ": { " lint:prettier ": " prettier --check docs/ ", " format:prettier ": " prettier --write docs/ " } } 今回はPrettierで検査対象にするファイルは docs/ ディレクト リに格納されるものとしてタスクを定義しました。 では、 docs/ ディレクト リに README.md を以下の内容で作成しましょう。 # Title Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Long Body Text Prettierの動作確認をしたいので、少し奇妙なファイルです。 タイトルの後ろに無意味な半角スペースがあったり、空行のみの行がスペースでインデントされていたりします。 この状態でPrettierを実行してみましょう。以下のコマンドを実行します。 bun lint:prettier 以下のように出力されました。 ファイルに問題があるようですね。しかし、今回の問題は自動的に対処できるものです。 では、以下のコマンドを実行しましょう。 bun format:prettier 実行結果は以下のように出力されます。 自動的な折り返しはされないようですね。しかし、余分な半角スペースは全て除去されています。 Prettierは基本的に設定の調整が不要なのですが、いくつか私の趣味に合わない部分があるので、そこだけは変えましょう。具体的には、タブと一行の最大幅と改行コードです。 タブは半角スペース2つに変換します。一行の幅はデフォルトの80文字から120文字に増やしましょう。改行コードはLFのみに固定します。 { " dependencies ": { " prettier ": " 3.6.2 " } , " prettier ": { " tabWidth ": 2 , " printWidth ": 120 , " endOfLine ": " lf " } , " scripts ": { " lint:prettier ": " prettier --check docs/ ", " format:prettier ": " prettier --write docs/ " } } あわせて、 .gitattributes ファイルを以下の内容で作成します。 text=auto eol=lf Remarkのインストール Remarkは JavaScript で実装された Markdown の処理ツールです。たくさんの プラグイン があり、処理ツールとしての機能を調節できるようになっています。 また、パーズしてASTになった Markdown をファイルに書き出せるような形式に永続化する機能もあります。 パーザのセットアップ まずは、Remarkのパーザ部分をセットアップしてみましょう。以下のコマンドで3つのモジュールをインストールします。 bun add remark-cli remark-frontmatter remark-gfm あわせて、bunから呼び出せるようにコマンドを追加します。 { " dependencies ": { " prettier ": " 3.6.2 ", " remark-cli ": " 12.0.1 ", " remark-frontmatter ": " 5.0.0 ", " remark-gfm ": " 4.0.1 " } , " prettier ": { " tabWidth ": 2 , " printWidth ": 120 , " endOfLine ": " lf " } , " scripts ": { " lint:md ": " remark --frail docs/ ", " lint:prettier ": " prettier --check docs/ ", " format:md ": " remark docs/ --frail --output ", " format:prettier ": " prettier --write docs/ " } } おっと、Remarkが追加したモジュールを動作するように構成していませんでした。 { " dependencies ": { " prettier ": " 3.6.2 ", " remark-cli ": " 12.0.1 ", " remark-frontmatter ": " 5.0.0 ", " remark-gfm ": " 4.0.1 " } , " prettier ": { " tabWidth ": 2 , " printWidth ": 120 , " endOfLine ": " lf " } , " remarkConfig ": { " plugins ": [ " remark-frontmatter ", " remark-gfm " ] } , " scripts ": { " lint:md ": " remark --frail docs/ ", " lint:prettier ": " prettier --check docs/ ", " format:md ": " remark docs/ --frail --output ", " format:prettier ": " prettier --write docs/ " } } remarkConfigにpluginsというプロパティがあるので、そこに追加したい プラグイン のモジュール名を列挙しています。今回は remark-frontmatter と remark-gfm ですね。 動作を確認していきましょう。とはいえ、既に用意した docs/README.md では何も起きないので新しいファイルを追加します。 docs/list.md というファイルを以下の内容で保存します。 --- author: taichi category: - example - sample --- # Title _foo bar baz_ ## Sub Title - [ ] one - [ ] two - [ ] three foo bar baz 1. first 1. second 1. third ---- frontmatterという YAML で メタデータ を付けた Markdown ですね。Listの部分では、 GitHub 固有の チェックボックス 記法を使っています。 Remarkの動作を確認してみましょう。以下のコマンドを実行します。 bun lint:md 特にエラーなく処理が終了するでしょう。 次は、フォーマットタスクを実行しましょう。以下のコマンドです。 bun format:md docs/list.md を開きなおしてみてください。興味深い変化が起きているはずです。 --- author: taichi category: - example - sample --- # Title *foo bar baz* ## Sub Title * [ ] one * [ ] two * [ ] three foo bar baz 1. first 2. second 3. third *** frontmatter部分に変化はありません。 次のTitleより下のイタリック体になっていた記号が変化しています。フォーマット前は _foo bar baz_ とアンダースコア記号を使っていましたが、フォーマット後は *foo bar baz* と アスタリスク が使われています。 リスト記法の - も * に変化しています。 同様に区切り線として、ハイフンを使っていたので ---- でしたが、フォーマット後は **** になっています。 最後の順序付きリストは、数字が昇順に調整されています。 これがRemarkに内蔵されている remark-stringify の正常な動作です。 Markdown をパーズしてASTにした後、メモリ上の表現を一定のルールで文字列に永続化するわけです。 このままでは、少し都合が悪いので調整しましょう。remarkConfigに settings プロパティを追加します。 { " dependencies ": { " prettier ": " 3.6.2 ", " remark-cli ": " 12.0.1 ", " remark-frontmatter ": " 5.0.0 ", " remark-gfm ": " 4.0.1 " } , " prettier ": { " tabWidth ": 2 , " printWidth ": 120 , " endOfLine ": " lf " } , " remarkConfig ": { " settings ": { " bullet ": " - ", " emphasis ": " _ ", " rule ": " - " } , " plugins ": [ " remark-frontmatter ", " remark-gfm " ] } , " scripts ": { " lint:md ": " remark --frail docs/ ", " lint:prettier ": " prettier --check docs/ ", " format:md ": " remark docs/ --frail --output ", " format:prettier ": " prettier --write docs/ " } } ここでは、 リストに使う記号として - 斜体やボールドなどを強調する際に使う記号として _ 区切り線として使う記号として - を設定しています。 では、もう一度フォーマッタを動かしてみましょう。 bun format:md 処理は成功し以下のように設定に基づいた表現になっているはずです。順序付きリストだけが昇順のままですね。 --- author : taichi category : - example - sample --- # Title _foo bar baz_ ## Sub Title - [ ] one - [ ] two - [ ] three foo bar baz 1. first 2. second 3. third --- これで分かったように、Remarkを Markdown のフォーマッタとして使う場合には、remarkConfigオブジェクトのsettingsプロパティを調整します。その設定項目は、 remark-stringify#options に記載されています。 RemarkとPrettierの整合性を取る Prettierはテキストファイルのフォーマットを整えるツールですが、実はRemarkのLintルールの中には、Prettierの動作と矛盾するものや重複するものが含まれています。 Prettierとうまく組み合わさらないものは、あらかじめ無効にしてしまいましょう。そのためのモジュールを以下のコマンドでインストールします。 bun add remark-preset-prettier モジュールを追加したら、そのモジュール名を プラグイン として追加します。以下のようになるでしょう。 { " dependencies ": { " prettier ": " 3.6.2 ", " remark-cli ": " 12.0.1 ", " remark-frontmatter ": " 5.0.0 ", " remark-gfm ": " 4.0.1 ", " remark-preset-prettier ": " 2.0.2 " } , " prettier ": { " tabWidth ": 2 , " printWidth ": 120 , " endOfLine ": " lf " } , " remarkConfig ": { " settings ": { " bullet ": " - ", " emphasis ": " _ ", " rule ": " - " } , " plugins ": [ " remark-frontmatter ", " remark-gfm ", " remark-preset-prettier " ] } , " scripts ": { " lint:md ": " remark --frail docs/ ", " lint:prettier ": " prettier --check docs/ ", " format:md ": " remark docs/ --frail --output ", " format:prettier ": " prettier --write docs/ " } } 次はフォーマットしがいのあるコンテンツを用意しましょう。docs/table.md として以下のようなファイルを作成します。 # Table | Column1 | Label | Description | | ----- | ----- | ----- | | val | aaaaaaa | Lorem ipsum dolor sit amet, consectetur adipiscing elit | | valval | aa | sed do eiusmod tempor incididunt ut labore | | foo | d | Duis aute irure dolor in reprehenderit in voluptate | | xxxxxxx | | deserunt mollit anim id est laborum | Prettierでフォーマットするコマンドは以下のとおりです。 bun format:prettier コマンドが正常に終了したら、どのようにフォーマットされるか確認してみましょう。 # Table | Column1 | Label | Description | | ------- | ------- | ------------------------------------------------------- | | val | aaaaaaa | Lorem ipsum dolor sit amet, consectetur adipiscing elit | | valval | aa | sed do eiusmod tempor incididunt ut labore | | foo | d | Duis aute irure dolor in reprehenderit in voluptate | | xxxxxxx | | deserunt mollit anim id est laborum | フォーマットする前のテーブルは正直言ってHTMLなりエディタの プレビュー機能 なりを使わなければ内容が把握できないものでしたが、Prettierのフォーマットによりテキスト表現だけでも読めるようになりましたね。 Remarkの一部としてPrettierを動かす RemarkによるフォーマットとPrettierによるフォーマットを毎回動かすのはやや面倒ですし、何かの拍子に抜けてしまいそうです。 というわけで、Remarkの処理の最後にPrettierを動かすための プラグイン を導入しましょう。 bun add unified-prettier これまで通り、pluginsに追加ですね。出力用の プラグイン なので remark-preset-prettier よりも後ろに配置します。 { "dependencies": { "prettier": "3.6.2", "remark-cli": "12.0.1", "remark-frontmatter": "5.0.0", "remark-gfm": "4.0.1", "remark-preset-prettier": "2.0.2", "unified-prettier": "2.0.1" }, "prettier": { "tabWidth": 2, "printWidth": 120, "endOfLine": "lf" }, "remarkConfig": { "settings": { "bullet": "-", "emphasis": "_", "rule": "-" }, "plugins": [ "remark-frontmatter", "remark-gfm", "remark-preset-prettier", "unified-prettier" ] }, "scripts": { "lint:md": "remark --frail docs/", "lint:prettier": "prettier --check docs/", "format": "remark docs/ --frail --output" } } PrettierとRemarkによるフォーマットは統合されたので、コマンド名はシンプルな format に変更しています。 Linterのセットアップ それでは、いよいよLinter機能をRemarkに追加しましょう。 Remarkが公式で提供しているLinterは、 remark-lint/packages に並んでいるのですが、この細かいルールを一つずつ選定していくのは面倒です。複数のルールを束にしたプリセットを公式から提供されていますので、それを使いましょう。 remark-preset-lint-recommended Markdown を使ううえで基本的なルールの集合です。今回はこれを採用します。 remark-preset-lint-consistent ドキュメント内で記法が一貫するように強制するルールの集合です。 それぞれの記法において、ファイル内で最初に登場した記法が一貫して使われているかどうかを検証できます。 今回は新規のプロジェクトを前提にしますので不採用です。 remark-preset-lint-markdown-style-guide Markdown Style Guide という極めて強い意見の Markdown に関するスタイルガイドを採用したルールです。 今回はそれほど多くのドキュメントがあるわけではないので不採用です。 まずは、モジュールをインストールします。 bun add remark-preset-lint-recommended モジュールをインストールしたら プラグイン として追加します。 { " dependencies ": { " prettier ": " 3.6.2 ", " remark-cli ": " 12.0.1 ", " remark-frontmatter ": " 5.0.0 ", " remark-gfm ": " 4.0.1 ", " remark-preset-lint-recommended ": " 7.0.1 ", " remark-preset-prettier ": " 2.0.2 ", " unified-prettier ": " 2.0.1 " } , " prettier ": { " tabWidth ": 2 , " printWidth ": 120 , " endOfLine ": " lf " } , " remarkConfig ": { " settings ": { " bullet ": " - ", " emphasis ": " _ ", " rule ": " - " } , " plugins ": [ " remark-frontmatter ", " remark-gfm ", " remark-preset-lint-recommended ", " remark-preset-prettier ", " unified-prettier " ] } , " scripts ": { " lint:md ": " remark --frail docs/ ", " lint:prettier ": " prettier --check docs/ ", " format ": " remark docs/ --frail --output " } } Remarkの プラグイン システムは、配列の後ろ側になるほどルールとして優先されますので、今回追加するプリセットはちょうど中ほどに入るように追加します。 lintのルールを追加したのですから、エラーが発生するような Markdown を作りましょう。docs/link.md を以下の内容で作成します。 # Links https : //example.com リンク記法が使われずにリンクのようなものがテキストとして記述されていますので、これは警告されるはずです。 Linterを実行するコマンドは以下のとおりです。 bun lint : md 実行すると確かに警告が出力され、プロセスがエラーで終了します。 リンク記法を使うべきであるという警告が出力されていますね。これが自動的に改善されるかどうか確認してみましょう。 bun format Linterを実行したときと同じエラーが出力されていますね。 では、 Markdown ファイルはどうなっているのでしょうか。 # Links < https://example.com > リンクが <> でくくられていますね。自動修正は機能しているようです。ただ、AIを使った開発においては、この出力だとAIが標準出力されたメッセージを根拠に解決を試みます。 つまり、当該ファイルを開くものの実際には修正済みという状態になってしまいますので、一定の混乱が発生します。 通信した トーク ン量で課金されたり、制限される今のAI相手にはこういう無駄が起きないようにしなければなりません。 この問題に対応するために設定を調整してみましょう。pluginsプロパティにモジュールを列挙すると常に使われてしまうので、Linterを動かすときとフォーマッタを動かすときで使うモジュールを切り替える必要があります。 remarkコマンドの -u オプションを使うと個別にモジュールを指定できます。 { " dependencies ": { " prettier ": " 3.6.2 ", " remark-cli ": " 12.0.1 ", " remark-frontmatter ": " 5.0.0 ", " remark-gfm ": " 4.0.1 ", " remark-preset-lint-recommended ": " 7.0.1 ", " remark-preset-prettier ": " 2.0.2 ", " unified-prettier ": " 2.0.1 " } , " prettier ": { " tabWidth ": 2 , " printWidth ": 120 , " endOfLine ": " lf " } , " remarkConfig ": { " settings ": { " bullet ": " - ", " emphasis ": " _ ", " rule ": " - " } , " plugins ": [ " remark-frontmatter ", " remark-gfm " ] } , " scripts ": { " lint:md ": " remark --frail docs/ -u remark-preset-lint-recommended -u remark-preset-prettier ", " lint:prettier ": " prettier --check docs/ ", " format ": " remark docs/ --frail --output -u remark-preset-prettier -u unified-prettier " } } この設定では、Linterとフォーマッタで共通して使う部分はremarkConfig以下に定義し、違う部分は各コマンドの引数として定義しています。 それでは、確認のため、補正済みのdocs/link.md を補正前に戻したうえで、Linterを動かしてみましょう。 エラーが出力されていますね。 では、フォーマッタを動かしてみましょう。 エラーや警告が出力されないので、プロセスは正常に終了します。 そして、 Markdown は自動補正されて正しくフォーマットされていますね。 # Links < https://example.com > ドキュメントのリンク切れを検証する 設計ドキュメントとして Markdown を使うということは、役割や責任範囲毎にファイルを分割していき、それらを ハイパーリンク で接続するという使い方が想定されます。 そうしたとき問題になるのが、ドキュメント間のリンク切れ問題です。ファイル名やファイルパスを変更することによって、リンク切れは頻繁に発生します。これをきちんとメンテナンスしていくのは、非常に面倒なタスクですよね。 そこで、Linterの一部としてリンク切れをチェックしエラーを標準出力することで、リンクのメンテナンスを生成AIに任せましょう。 Remarkにはリンク切れを検証する プラグイン がいくつかあるので、それらをまとめてインストールしましょう。 bun add remark-validate-links remark-lint-no-dead-urls remark-validate-links が同じ ディレクト リツリー内にあるファイルへのリンクを検証する プラグイン です。それに対して、 remark-lint-no-dead-urls はリンクとして記載されているURLにHTTPリク エス トを送信して機能しているか検証する プラグイン です。 これをLinter用のコマンドに組み込むとこうなります。 { " dependencies ": { " prettier ": " 3.6.2 ", " remark-cli ": " 12.0.1 ", " remark-frontmatter ": " 5.0.0 ", " remark-gfm ": " 4.0.1 ", " remark-lint-no-dead-urls ": " 2.0.1 ", " remark-preset-lint-recommended ": " 7.0.1 ", " remark-preset-prettier ": " 2.0.2 ", " remark-validate-links ": " 13.1.0 ", " unified-prettier ": " 2.0.1 " } , " prettier ": { " tabWidth ": 2 , " printWidth ": 120 , " endOfLine ": " lf " } , " remarkConfig ": { " settings ": { " bullet ": " - ", " emphasis ": " _ ", " rule ": " - " } , " plugins ": [ " remark-frontmatter ", " remark-gfm " ] } , " scripts ": { " lint:md ": " remark --frail docs/ -u remark-preset-lint-recommended -u remark-preset-prettier -u remark-validate-links=repository:false -u remark-lint-no-dead-urls=deadOrAliveOptions:{timeout:10000} ", " lint:prettier ": " prettier --check docs/ ", " format ": " remark docs/ --frail --output -u remark-preset-prettier -u unified-prettier " } } Linter用のコマンドが長くなり始めていますね。必要な所だけ抜粋して説明します。 内部リンクを検証する プラグイン の設定部分がこれです。 -u remark-validate-links=repository:false remarkコマンドのオプションとして、モジュール毎の設定を記述するには、モジュール名の最後に = を付けたうえで、最外周の {} がないJSON5を記述します。 remark-validate-linksは、検証対象がgit リポジトリ でかつoriginが設定されていることを前提に動作するので、その機能を無効化するために repository:false を設定しています。 普段ならこの設定は不要でしょう。 外部リンクを検証する プラグイン の設定部分がこれです。 -u remark-lint-no-dead-urls=deadOrAliveOptions:{timeout:10000} remark-lint-no-dead-urls は dead-or-alive というライブラリを内部的に使っています。その タイムアウト オプションをここでは設定しています。 これらの プラグイン が機能するか確認するために、先ほどの docs/links.md に手を加えて内部リンクと外部リンクを追加してみましょう。 # Links < https://example.com > ## Internal Links * [ Exists ]( #Links ) * [ NotExists ]( #likns ) ## External Links * [ External Example ]( https://example.com ) * [ NotExists External ]( https://example.com/fail ) では、リンク切れを検証してみましょう。以下のコマンドを実行します。 bun lint:md 実行結果はエラー終了になります。 タイポしているページ内のリンクと、存在しない外部ページに対するリンクが警告やエラーとして検出されていますね。 Linter用の設定ファイルを外部化する CLI オプションとしてJSON5を記述するというのは、分かり辛いので設定ファイルを外部化しましょう。 それではフォーマッタの設定から分離します。設定ファイルを config/remark-format.json に作成してください。 { " settings ": { " bullet ": " - ", " emphasis ": " _ ", " rule ": " - " } , " plugins ": [ " remark-frontmatter ", " remark-gfm ", " remark-preset-prettier ", " unified-prettier " ] } フォーマッタの設定はパーザとシ リアラ イザだけなのでシンプルですね。 次にLinterの設定ファイルを分離します。 config/remark-lint.json に作成します。 { " settings ": { " bullet ": " - ", " emphasis ": " _ ", " rule ": " - " } , " plugins ": [ " remark-frontmatter ", " remark-gfm ", " remark-preset-lint-recommended ", " remark-preset-prettier ", [ " remark-validate-links ", { " repository ": false } ] , [ " remark-lint-no-dead-urls ", { " deadOrAliveOptions ": { " timeout ": 10000 } } ] ] } このエントリではやりませんが、Linter側の設定はまだかなり調整の余地があるでしょう。 最後にpackage. json から不要になった設定を削除して、コマンドを調整します。 { " dependencies ": { " prettier ": " 3.6.2 ", " remark-cli ": " 12.0.1 ", " remark-frontmatter ": " 5.0.0 ", " remark-gfm ": " 4.0.1 ", " remark-lint-no-dead-urls ": " 2.0.1 ", " remark-preset-lint-recommended ": " 7.0.1 ", " remark-preset-prettier ": " 2.0.2 ", " remark-validate-links ": " 13.1.0 ", " unified-prettier ": " 2.0.1 " } , " prettier ": { " tabWidth ": 2 , " printWidth ": 120 , " endOfLine ": " lf " } , " scripts ": { " format ": " remark docs/ --frail --output -r config/remark-format.json ", " lint ": " remark --frail docs/ -r config/remark-lint.json " } } Prettierを直接利用するケースは基本的にありませんので、あわせてコマンドをシンプルにしておきました。 まとめ 今回のエントリでは、順を追ってPrettierの導入からRemarkによるフォーマット、その後Linterを導入し、最後は設定ファイルを切り出して調整しやすい状態にしました。 ここで紹介した手法を使えば、生成AIが出力する大量のテキストファイルのファイルフォーマットや構成を一定の水準に保てるようになります。 生成AIは出力した文章の中身について理解しておらず、それっぽいものを出力するのみですが、こうやって自動的に対処できる部分については、AIに対処してもらうことで私たちはより意味のあるレビューを行えるようになるはずです。 このエントリを読んだ皆さんが、より高品質なドキュメントを元にソフトウェア開発が実施できるようになることを願っています。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sato.taichi レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
こんにちは。グループ経営ソリューション事業部 グループ経営 コンサルティング 第2ユニット サイクロス製品開発部の座間です。 この記事は、 MCP が提供しているElicitationという機能が公式TypeScript SDK で使用できるようになっていたので、キャッチアップがてら少し触ってみたという内容でお送りします。 Elicitationについて 定義 公式ドキュメント ではElicitationについて以下のように記載されています。 The Model Context Protocol ( MCP ) provides a standardized way for servers to request additional information from users through the client during interactions. This flow allows clients to maintain control over user interactions and data sharing while enabling servers to gather necessary information dynamically. つまり、Elicitationは「 MCP Serverからユーザーに追加の情報を要求できる標準化された方法」と言えそうです。 Elicitationは公式にはdraft段階ですが、実装側ではTypeScript SDK 、 MCP ホスト側では GitHub Copilotなどがすでに対応しています。 使い道 MCP公式TypeScript SDKのREADME 記載の実装例では、おおまかに以下のような流れの中でElicitationを使用しています。 ユーザーが日時を指定してレストランを予約するようエージェントに指示する エージェントは指定された日時とレストラン名で予約を試みるが、すでに予約が埋まってしまっている エージェントはElicitation機能を利用して、ユーザーに別の日時を選択することを求める エージェントはユーザーから受け取った新しい日時を基にレストランを予約する 上記フローはユーザーが見える範囲にフォーカスして記述しています。 実際にElicitationを使う判断をしているのは MCP Server Toolです。 「Toolの実行途中に追加の情報を取得できる」という特性を考えると、上記以外にも以下のようなことに使えそうですね。 Toolが途中まで処理した結果をユーザーに提示し承認を求める。OKなら処理を進め、NGなら実行をキャンセルする。 Tool呼び出し時にエージェントが推論で補完しきれなかった引数をユーザーが追加で入力するように求める。 MCP 公式TypeScript SDK で実装してみた まずはElicitationがどのようなものなのかを感じ取るために、挨拶をしてくれるだけの簡単な MCP Serverを実装してみます。 環境 Node.js 18.20.8 @modelcontextprotocol/ sdk 1.17.3 MCP Serverを利用するホストとしては、 GitHub Copilotを VS Code から使用します。 MCP Serverの実装 最初に、ただテキストを返すだけの MCP Serverを実装します。詳細な実装方法は 公式のクイックスタート 等、すばらしい記事がたくさんあるので割愛します。 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" ; const server = new McpServer( { name : "elicitation-tutorial" , version : "1.0.0" , } ); server.tool( "Greet" , "Say Hello to you" , {} , async () => { return { content : [ { type : "text" , text : `こんにちは` } ] } ; } ); async function main () { const transport = new StdioServerTransport(); await server.connect(transport); console .error( "MCP Server running on stdio" ); } main(). catch (( error ) => { console .error( "Fatal error in main():" , error); process .exit( 1 ); } ); Elicitation機能の追加 上記で作成した MCP ServerにElicitation機能を追加してみましょう。TypeScript SDK では elicitInput というメソッドを使用します。 let yourName: string | undefined ; const result = await server.server.elicitInput( { message : "あなたについて教えてください" , requestedSchema : { type : "object" , properties : { yourName : { type : "string" , title : "氏名" , description : "あなたの名前を入力してください" } } , required : [ "yourName" ] } } ); if (result. action === "accept" && result.content && result.content.yourName) { yourName = String (result.content.yourName); } 少しだけ解説します。 elicitInput で指定する引数の構造は以下のとおりです。これ以外にも指定できる項目もあるので、詳細は MCPの公式ドキュメント などをご確認ください。 message : ユーザーに入力を求めるときに表示するテキスト requestedSchema : 期待するレスポンスの形式を定義する JSON スキーマ type : 期待するデータ型だが、基本的に構造化データを受け取りたいので object 固定 properties : ユーザーに入力を求めるプロパティ ${プロパティ名} : 上記の例では最初に定義した yourName を受け取りたいので yourName を指定 type : 期待するデータ型。 string number boolean から指定可能 title : タイトル description : プロパティについての説明 required : 入力を必須とするプロパティ名の配列 また、 elicitInput メソッドの戻り値には action 属性が含まれます。 action はユーザーの操作結果によって以下の3種類の値をとります。 accept : ユーザーが承認し、データを入力した decline : ユーザーが追加の入力を明示的に拒否した cancel : ダイアログを閉じる等でユーザーが明示的に選択せずに終了した Elicitationを組み込んだserver.toolの実装は以下のようになります。 server.tool( "Greet" , "Say Hello to you" , {} , async () => { let yourName: string | undefined ; const result = await server.server.elicitInput( { message : "あなたについて教えてください" , requestedSchema : { type : "object" , properties : { yourName : { type : "string" , title : "氏名" , description : "あなたの名前を入力してください" } } , required : [ "yourName" ] } } ); if (result. action === "accept" && result.content && result.content.yourName) { yourName = String (result.content.yourName); } return { content : [ { type : "text" , text : `こんにちは、 ${ yourName } さん` } ] } ; } ); これで、挨拶をしようとするとユーザーの名前を聞いてくるToolの完成です。早速使ってみましょう! まずは GitHub Copilotに挨拶ツールを使わせます。すると message で定義したメッセージが表示され、名前を入力するよう求められます。 Respondボタンを押すと上部に入力ボックスが出てくるので、名前を入力します。 名前を渡すとToolの実行が再開します。無事入力した名前で挨拶してくれました。 このようにElicitationを利用すると、Toolの実行を中断して追加の情報を与えることができます。 試してみた:Elicitationを組み込んだ健康アドバイザーToolの実装 ここまででElicitationについてざっと理解できたので、ちょっとした遊びも兼ねて健康アド バイス を実施してくれるToolを実装してみました。 あらかじめ設定しておいた質問をエージェントが順々に投げかけ、集めた回答をもとに簡単な健康アド バイス を提供してくれます。 実演 「健康診断して」と伝えて開始します。基本情報を求められるので入力します。 最初は睡眠に関する質問です。 type: number を指定しているので、数値以外はエラーが出るようになっています。 properties で複数のプロパティを定義すると、Respondボタンは表示されずにそのまま次の質問に移ります。プロパティの中で enum を定義すると選択式にもできます。 続いて運動習慣に関する質問です。 type: boolean を使用するとtrue/falseで選択できます。 最後に朝食に関する質問です。 required に含めなかったプロパティは None No selection を選択してスキップすることもできます。 質問をすべて入力し終えるとToolが最後まで実行されます。健康アド バイス を実施する旨を記載したプロンプトを戻り値にしているため、Toolの実行結果を受け取ったエージェントが考えてアド バイス をしてくれます。 無事アド バイス をしてくれました!現在の実装では質問を固定していますが、質問自体も生成AIが考えられるようにしても面白そうですね。 実装 だいぶ長いですが参考として実装も載せておきます。基本的には最初に実装した elicitInput を少し変えながら連続して配置しています。 今回は action: accept のパターンしか処理を書いていませんが、実際は action: decline や action: cancel の場合の処理も書いてあげた方が良いです。ユーザーがRespondではなくCancelボタンを押した場合でもToolの実行は継続され、次のセクションの質問に移ってしまいます。 server.tool( "CheckHealth" , "いくつかの質問からユーザの健康状態の診断と改善アドバイスを実施します" , {} , async () => { let userName: string | undefined ; let age: number | undefined ; let sleepTime: number | undefined ; let sleepQuality: string | undefined ; let doesExerciseRegularly: boolean | undefined ; let hasBreakfastEveryday: boolean | undefined ; let breakfastMenu: string | undefined ; const userInfoResult = await server.server.elicitInput( { message : "まずは、あなた自身について教えてください" , requestedSchema : { type : "object" , properties : { name : { type : "string" , title : "氏名" , description : "あなたの名前を教えてください" } , age : { type : "number" , title : "年齢" , description : "あなたの年齢を教えてください" } } , required : [ "name" , "age" ] } } ); if (userInfoResult. action === "accept" && userInfoResult.content) { if (userInfoResult.content. name ) { userName = String (userInfoResult.content. name ); } if (userInfoResult.content.age) { age = Number (userInfoResult.content.age); } } const sleepInfoResult = await server.server.elicitInput( { message : ` ${ userName } さんの睡眠について教えてください` , requestedSchema : { type : "object" , properties : { question1 : { type : "number" , title : "睡眠時間" , description : "あなたの平均的な睡眠時間を教えてください" } , question2 : { type : "string" , title : "睡眠の質" , description : "最近よく眠れている実感はありますか?" , enum : [ "常にある" , "ある" , "あまりない" , "ない" ] } } , required : [ "question1" , "question2" ] } } ) if (sleepInfoResult. action === "accept" && sleepInfoResult.content) { if (sleepInfoResult.content.question1) { sleepTime = Number (sleepInfoResult.content.question1); } if (sleepInfoResult.content.question2) { sleepQuality = String (sleepInfoResult.content.question2); } } const exerciseInfoResult = await server.server.elicitInput( { message : ` ${ userName } さんの運動習慣について教えてください` , requestedSchema : { type : "object" , properties : { question3 : { type : "boolean" , title : "運動習慣" , description : "日常的に運動していますか?" } } , required : [ "question3" ] } } ) if (exerciseInfoResult. action === "accept" && exerciseInfoResult.content && exerciseInfoResult.content.question3 !== undefined ) { doesExerciseRegularly = Boolean (exerciseInfoResult.content.question3); } const breakfastInfoResult = await server.server.elicitInput( { message : ` ${ userName } さんの朝食事情について教えてください` , requestedSchema : { type : "object" , properties : { question4 : { type : "boolean" , title : "毎日食べているか" , description : "朝ごはんは毎日食べていますか?" } , question5 : { type : "string" , title : "朝食の内容" , description : "よく食べる朝ごはんのメニューを教えてください" } } , required : [ "question4" ] } } ) if (breakfastInfoResult. action === "accept" && breakfastInfoResult.content) { if (breakfastInfoResult.content.question4 !== undefined ) { hasBreakfastEveryday = Boolean (breakfastInfoResult.content.question4); } breakfastMenu = breakfastInfoResult.content.question5 ? String (breakfastInfoResult.content.question5) : "未回答" ; } const prompt = [ `以下の情報を基に ${ userName } さんの健康状態を判断し、適切なアドバイスをしてください。` , `・年齢: ${ age } 歳` , `・睡眠時間:平均 ${ sleepTime } 時間` , `・日頃から眠れているか: ${ sleepQuality } ` , `・日常的に運動しているか: ${ doesExerciseRegularly } ` , `・朝食を毎日食べているか: ${ hasBreakfastEveryday } ` , `・朝食の内容: ${ breakfastMenu } ` ] . join ( ' \n ' ); return { content : [ { type : "text" , text : String ( prompt ) } ] } ; } ); おわりに 本記事では、 MCP の機能の1つであるElicitationについて、簡単な実装例とともに解説しました。 「Toolの実行途中にユーザーが入力できるボックスを表示させる」という特性は様々な用途に使えそうですね。 MCP にはElicitation以外にもSamplingなど面白そうな機能がまだまだあるので、色々試していきたいです。 最後までお読みいただきありがとうございました!本記事が少しでも皆様のご参考になれば幸いです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @zama.kazuki レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
こんにちは。 電通 総研ITの寺尾です。 今回は IntelliJ の リファクタリング 機能 の実装方法についてご紹介します。 前回はこちら: IntelliJプラグイン開発の始め方~コード補完編~ リファクタリング とは クラス名や変数名を変更した際、それを参照している別コードでも一緒にリネームしてくれる、パッケージ移動した際に変更後の状態に合わせてパッケージ名を変更してくれる機能を自然と使用している開発者は多いと思います。 プラグイン 開発では IntelliJ に拡張ポイントを実装し、任意の処理を リファクタリング に合わせて実行させることができるようになります。 実装前に知っておくこと 前回と同様に、本記事でもKotlinでの実装例を提示します。 Kotlinプロジェクトに関する内容は、アクション機能編の 実装前に知っておくこと をご参照ください。 実装 ゴール 今回は、DAO名を変更した際に対応する SQL ディレクト リ名をリネームする拡張ポイントの実装をします。 事前準備 IntelliJ プラットフォーム プラグイン の SDK では、特定の要素に専用の プロセッサー クラスが用意されています。 今回はクラス名のリネーム用クラス RenameJavaClassProcessor を使用して拡張ポイントを実装します。 まずは以下の準備から始めていきましょう。 RenameJavaClassProcessor のサブクラス プラグイン 設定登録 リファクタリング 機能は拡張ポイントとして登録します。 plugin.xml には、以下のように <renamePsiElementProcessor> タグを使って プロセッサー クラスを登録します。 IntelliJ の拡張ポイントとして登録するため、 <extensions defaultExtensionNs="com.intellij"> タグの中に記述します。 <renamePsiElementProcessor implementation = "org.domaframework.doma.intellij.refactoring.dao.DaoRenameProcessor" order = "first" /> プロセッサー クラスの実装 プロセッサー クラスでは、以下のオーバーライドメソッドを実装します。 canProcessElement :リネーム時、 プロセッサー の実行条件を満たしているかを判定する renameElement :リネーム前の要素情報とリネーム後の名称を受け取り任意の処理を実行 処理対象の判定 DAO名のリネーム以外で SQL ディレクト リ名が変更されないように、しっかり事前チェックをしておきます。 canProcessElement の中で、以下のように処理対象であるかを判定します。 getDaoClass() は、引数の要素がDAOが存在することを判定する独自のメソッドです。任意の処理で同様にチェックしてみてください。 override fun canProcessElement(element: PsiElement): Boolean = super .canProcessElement(element) && getDaoClass(element.containingFile) != null リネーム処理本体 DAOのリネームによって変更前と後の情報を受け取り、変更前のDAO名情報を基に SQL ディレクト リ側もリネームします。 このメソッドは、リネーム処理が呼び出された際に DAO側のリネームが実行される前 に処理されます。 そのため、 最後に親の renameElement() を呼び出さないと、DAO側はリネームされないため注意してください。 以下サンプルで呼び出している getContentRoot() や getPackagePathFromDaoPath() は独自に実装している拡張関数になるため、「 Doma Tools」のコードも参考に実装してみてください。 override fun renameElement( element: PsiElement, -- リネーム前の要素情報 newName: String , -- リネーム後の要素名 usages: Array < out UsageInfo>, -- 対象要素の参照リスト listener: RefactoringElementListener?, ) { // 変更前要素情報からDAOクラス情報を取得 val daoClass = getDaoClass(element.containingFile) ?: return val project = element.project val virtualFile = element.containingFile.virtualFile ?: return // DAOパッケージ名を基にSQLディレクトリ要素を取得 project.getContentRoot(virtualFile)?.let { element.module?.getPackagePathFromDaoPath(virtualFile)?.let { // ディレクトリ名がDAOクラス名と一致するディレクトリ名をリネーム if (it.name == daoClass.name) { it.rename(it, newName) } } } // 親のリネーム処理を呼び出す super .renameElement(element, newName, usages, listener) } 動かしてみる デバッグ 環境でDAOクラス名やファイルのリネームによって対応する SQL ディレクト リ名も一緒に変更されることを確認して見ましょう。 デバッグ 起動方法は前回と同じく、 デバッグ起動する で実行してください。 参考: 「 Doma Tools」実装コード 「 Doma Tools」の リファクタリング 機能 は、以下コードで実装しています。 DaoRenameProcessor さいごに 今回は リファクタリング 機能の実装についてご紹介しました。 「 Doma Tools」では Doma のDAOと SQL の紐づけに対応し、ファイルジャンプだけでなくファイル名の連動も強化しています。 特定のファイル間での連携が必要なプロジェクトにおいて、 リファクタリング 機能を用意して開発効率を向上させましょう! 「 Doma Tools」へのレビューも投稿していただけますと大変励みになります🙇‍♀️ Doma Tools マーケットプレースページ 本 プラグイン は OSS として Domaコミュニティ へ寄贈されています。 不具合修正や機能要望、ディスカッションにもぜひご参加ください。 採用ページ 執筆: @terao.haruka レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
みなさんこんにちは、XI本部エンジニアリングオフィスの佐藤太一です。 このエントリでは、私がRustで実装した Java 用バージョンマネージャであるKopiを紹介すると共に、実装の過程で得た生成AIを使ったソフトウェア開発に関する知見を共有します。 Kopiの ソースコード とドキュメントは全てClaude Codeによるものです。私自身は開発環境の構築とメンテナンスをしながら、プロンプトによる指示のみで、約2か月弱の期間に約四万行のRustコードと約六千行の Markdown を書き上げました。行数の計測においては、コメントや改行は除いています。 成果物は全て オープンソース ソフトウェアとして公開していますので、興味を持ったら是非、公式サイトに来てください。 https://kopi-vm.github.io/ Kopiの紹介 ここでは簡単にKopiを紹介させてください。 Java 用バージョンマネージャとは何か 要はローカルにインストールした複数の Java を必要に応じて切り替えられるソフトウェアのことです。 この手のソフトウェアで私が最初に使ったのは恐らくrvmです。次々とリリースされる Ruby の新しいバージョンに追従するために、気軽に使える インストーラ ーとして使っていたと記憶しています。 今やどの プログラミング言語 にも開発ツールキットを切り替えられるソフトウェアが提供されています。 Python ならvenv、uv、pyenvがあり、Nodeならvolta、nvm、nがあり、様々な言語を統一的に扱える asdf やanyenv、mise、aquaといったツールがあります。 もちろん、 Java にもバージョンマネージャはいくつかあります。jenv、jabba、sdkmanあたりがよく使われているんじゃないでしょうか。 Java 用バージョンマネージャをなぜ新しく作るのか 生成AIに渡す課題として、大きすぎず小さすぎず実用性があり私が作りたいからです。検証課題として、どのようなことを考えていたのかは後述します。 一応、それっぽい説明をしておくと、 Java 専用に作られており、複数のプラットフォームで動作し、メンテナンスが継続しているものがないからです。 jenvやsdkmanは シェルスクリプト の塊なので Windows では動作しません。jabbaはGoで実装されているので複数のプラットフォームで動作しますが、もう何年もメンテナンスされていません。 ちなみに、複数のプラットフォームで動作する様々な言語やツールに対応したバージョンマネージャとしては、miseがあります。 Rustで実装されているmiseは非常に高機能で、複数の言語を管理するだけでなく、 環境変数 をプロジェクトごとに調整しながら、タ スクラン ナーとしても動作します。 Java のバージョン管理機能もコア機能として含まれています。今の時点で、Kopiのmiseに対する優位性は Java 専用の検索機能を提供していることと、再現性のあるフォーマットで詳細にバージョンを指定できることです。 インストール手順 どういうツールなのか分かったところでインストールの方法を説明しましょう。 OSとしては、 Windows 、 Mac 、 Debian / Ubuntu をサポートしています。CPUについては Intel 系とARM系の両方をサポートしています。 Windows11でのインストールは以下のコマンドを実行してください。 winget install kopi macOS でのインストールはHomebrewを使います。 brew install kopi-vm/tap/kopi Debian / Ubuntu はgpgキーのインストールなど少々ややこしいので シェルスクリプト を実行する形式です。中身は自前のPPA リポジトリ から deb パッケージをダウンロードするようになっています。 curl -fsSL https://kopi-vm.github.io/install.sh | bash Rustで実装したツールですので、crates.io にも公開してありcargoでインストールできます。 cargo install kopi 各パッケージマネージャでのインストールが終わったら、setup コマンドを実行します。 kopi setup 必要な ディレクト リが作成されます。続いてshims ディレクト リをPATH 環境変数 に登録するように利用中のシェルに応じて指示が表示されますので、それに従って 環境変数 を調整してください。 例えば、 bash では以下のように指示が表示されます。 基本的な使い方 インストール方法が分かったところで次は基本的な使い方を説明します。 まずは、自分のプロジェクトで使う Java を決めて設定ファイルを作成しましょう。 Kopiは専用の記法で選択的に Java をインストールできます。 # 一番基本的なインストール。この場合ディストリビューションはTemurinになります。 kopi local 21 # ディストリビューションを指定する場合、@の手前にディストリビューション名を指定します。 kopi local corretto@24.0.2 # JRE を指定する事もできます。 kopi local jre@zulu@17.0 これによって、 .kopi-version というファイルが出力されているはずです。例えば、一番上のコマンドを実行した後の中身は以下のようにインストールした Java のバージョンが記載されています。 temurin@21.0.8 この状態で java コマンドを実行してみましょう。以下のように出力されます。 java --version openjdk 24.0.2 2025-07-15 OpenJDK Runtime Environment Corretto-24.0.2.12.1 (build 24.0.2+12-FR) OpenJDK 64-Bit Server VM Corretto-24.0.2.12.1 (build 24.0.2+12-FR, mixed mode, sharing) 今度は Java の ディストリビューション を検索してみましょう。検索もインストールするのと同じ記法が使えます。 kopi search zulu@21 検索結果はこうなります。結果の最上部にあるものが実際にはインストールされるわけですね。2段目以降をインストールしたければ、より詳細にバージョン番号やビルド番号を指定しましょう。 最後は一時的に違うバージョンの Java を使ってみましょう。 kopi shell oracle_open_jdk@21 このコマンドを実行すると、現在使っているシェルの子プロセスとして指定した Java が使えるシェルが起動します。 生成AIを使ったソフトウェア開発 Kopi の基本的な機能を説明しましたので一定の実用性があるソフトウェアであることは、ご理解いただけたでしょう。 ここからは、Kopiをどのような手法で開発したのかを説明します。 KopiはRustを使って実装していますが、全ての ソースコード をClaude Codeが記述しています。改行の一つも私は出力されたものに手を入れていません。 Markdown で書かれたドキュメント類も同様です。 私がこの開発において作業しているのは、開発環境のセットアップ部分です。例えば、Rustの開発環境を構築するための Cargo.toml や rust-toolchain.toml は私が手書きしています。 また、CI/CD環境である GitHub Actionsのワークフローについても私が手書きしています。 GitHub にはコミットしていませんが、開発に使っているDevContainerの構築についても私が用意しました。 Kopiの開発における課題設定 Kopiの開発は実用性があるソフトウェアを、生成AIでどこまで作れるのか?という研究開発の一環で行っているものです。 最終的には、弊社の主たるビジネスである受託開発やパッケージ開発において、生成AIを導入するための足がかりの一部となることを企図しています。 この開発を行うにあたって、最も重要な検証項目はコードを書く作業を一体どこまでなら生成AIに任せられるのか?ということです。 そこで、私自身が取り扱うことの出来ない言語を採用することで手出しが出来ないようにする必要がありました。そのために実装言語としてRustを選んでいます。お恥ずかしいことですが、私自身はどうしても借用の概念が理解できず、いまだにRustを手書きするといつまでたっても コンパイル が通りません。 これは、受託開発やパッケージ開発においてコードを大筋で読むことはできるが書くことは出来ない業務のエキスパートが生成AIを使った場合に、どのような印象を受けるかという視点を模倣したいという意図があります。 生成AIを使った開発の生産性 まずは、分かりやすい生産性指標としての生産コード行数を提示しつつ、この八週間くらいの間にどのような心境の変化があったのか説明しましょう。 ここでは、ノウハウにあたる部分については説明しませんので、読み飛ばして貰っても結構です。 まずは、コードの変更量グラフです。六月の中旬から開発を始めて、約一週間ごとに棒グラフになっています。最初の週が最も生産量が多く約二万行のコードを出力しています。これをピークにその後は徐々に生産量が減っていき、開発がほぼ終了した七月の最終週には一万三千行くらいになっています。 恍惚とした最初の一週間 最初の週はAIハッピーとも言える恍惚とした体験でした。Rustという自分では全く扱えない言語のコードを生成AIが想像を絶する速度で出力していき、それらが全て何となく動いているのです。 私にとって最も得意な プログラミング言語 は Java とTypeScriptですが、それらを使ったうえで働き慣れたプロジェクトで完全に整備された開発環境と疑う余地なく確定した業務仕様が存在するとしても、一週間で二万行のコードを生産することは不可能です。 この時点ではまだ、Claude Codeを使い始めたばかりで、暗中模索といった状況でさえこの量の動くコードが出力されるのですから、プロセスをきちんと整備し私自身が生成AIを使い慣れていけば、一人で月間十万行程度のコードは実装できるのではないかと考えていました。つまり、三か月もあれば三十万行程度のシステムが作れる……。 普通のSI案件なら十人単位の人間が働いてもおかしくない規模感です。そして重要なこととして生成AIに仕事を依頼中は プログラマ の手は空くのです。つまり、PMと一緒になって顧客折衝に参加できます。この時点で私は生成AIの登場によって世界はひっくり返ったと感じていました。 リファクタリング 地獄とプロセス整備の二週目、三週目 夢のような時間は長続きしないものです。 できあがった成果物を冷静になって評価してみると、間違いなく機能しているコードはあるものの、大半はそれっぽく名付けられているのがよく見ると意味不明な関数名やモジュールが一貫性なく定義されており、コーディングスタイルもバラバラです。見せかけだけで何もテストしていないテストコードや何も計測していない ベンチマーク 、そういった実際には役に立たないものが数万行規模であふれていたのです。 二週目と三週目はほとんど機能を追加することなく、コードの整理と名前付けルールの整備、 開発プロセス の整備を行っていました。前の週と違って全く進捗を感じられない低調で辛い時間です。ここで、自らが持つ生成AIに対する過度な期待が打ち砕かれてしまいました。 この時期から、生成AIがある種の コンプリートガチャ ( コンプガチャ )であると考え始めます。 コンプガチャ とは、複数のアイテムをそろえることで特別な報酬を得られるガチャシステムのことです。2012年ごろに大きな社会問題化し規制されることになりました。 コンプガチャ が規制されているのは、複数のアイテムをそろえることについて人間の認知する確率と実際の確率に大きな乖離があり、その乖離を意識しないままにガチャを回すことに熱狂してしまうからです。 Claude Codeの制約が急にきつくなった四週目 生成AIが コンプガチャ であると考えることで、自分の精神状態がどうなっているのか客観視できるようになります。 ソフトウェア開発におけるコンプリート状態を定義するのは少し難しいものの、全ての機能が手書きしたときと同程度のソフトウェア品質で完成した状態を100%と仮定してみましょう。 コンプガチャ なので、四割から五割くらいまでは比較的容易に到達します。しかし、八割を超えたあたりから急速にアタリの頻度が低減します。 このように考えると、生成AIを使ったソフトウェア開発に対する熱狂と幻滅が起こる原理に近づいている感じがしませんか? 私が契約しているのはMAXプランの20倍ですが、DevContainerで構築した私の環境ではclaudeプロセスを複数同時に起動していると数分で一つを残してプロセスがシャットダウンされるようになってしまいました(8月現在は、その制限はなくなっているようです)。それまで、常時3つから4つくらいのclaudeプロセスを動かしていたので、成果物の生産量に大きくブレーキがかかってしまいます。ホストOS側の Windows とDevContainer内の二つ同時に動かすことは出来ていたので生産力が激減という程でもなかったのが幸いでした。 プロセスを磨き続けた五週目、六週目 JDK の メタデータ を取得するために使っているFoojayのデータが突然吹き飛んで何もデータが取れなくなるなど、アクシデントはいくつかあるものの順調に開発が進んでいきます。 安定して一万行程度のコードを生産しているように見えますが、実は五週目の中盤から六週目の前半にかけて夏休みをとっていたので、その間は全く稼働していませんでした。 ということは、この時点でかなり生産量が戻ってきているのです。しかし、六週目には当初に予定していた機能の実装が全て完了してしまいます。 生成AIの限界に到達した七週目 八月の第一週は露骨にコードの生産量が低減しています。これはなぜかというと、機能の実装が完了して各OS固有のパッケージングを実装し始めたからです。 例えば、 Windows では WiX Toolsetを使って msi に実行バイナリをパッケージングしています。まず、 Wix という語がインターネット上では二つの全く違うものを指しています。一つは Visual Studio から分離した インストーラ ーの作成ツール、もう一つはWebサイトを簡単に作れる Wix .com という SaaS です。 検索エンジン で知識を収集すると一定の混乱があるわけですね。このような状況では生成AIはほとんど機能しません。話を難しくする状況として、広く使われている WiX Toolsetはv3ですが、v4で大きな変更があった後、急速にバージョンアップが行われておりv3とv4のメンテナンスが終了し、現在はv6が最新です。公式のドキュメントは比較的充実していますし、 MSDN にもドキュメントはそれなりに存在するのですが、生成AIにとって都合の良い形をしていないようです。 どうしてもv3対応の構成定義をしてしまいv6でビルドしようとしてエラーになる。細かく指示を出してv6の記法を使わせても、少しでも曖昧な指示をするとv3の記法を混ぜ込んでしまうことで混乱し、ビルドが失敗するようになる。こういうことを繰り返すので全く進捗しなくなってしまいました。 結果的に、 Wix Toolset、Homebrew、nfpmを用いた構成定義と、それらを GitHub Actionsでパッケージングするワークフローは私が手書きしています。 見積り精度に現実味がでてきた八週目 パッケージングの動作確認としてインストールテストをしている最中に、比較的大きなバグを見つけてしまいました。八週目で突然コードの変更量が一万行にも及んでいるのは、このバグに対応する作業を実施したからです。Kopiは macOS でも動作するのですが、私が macOS を普段使いしていないために、Application Bundleという ディレクト リ構成に関する知見がなく、それを反映したアプリケーションの実装になっていなかったのです。 しかし、ここまでに蓄積した 開発プロセス に基づいて、技術調査、機能設計、実装計画の立案という流れが安定していたので淡々と機能実装を進められました。 生成AIを使った 開発プロセス ここからは、この八週間で得た生成AIを使ったソフトウェア開発において私が蓄積した知見を説明します。 成果物の出力は基本的に英語でやるべき 生成AIが持っている知識や情報の検索処理は基本的に英語で実施されているようです。 生成AIの出力成果物であるドキュメントや ソースコード 、プロンプトの応答はコンテキストの永続化モデルです。 つまり、出力成果物は英語にしておくと翻訳タスクが実行されない分コンテキストウィンドウの消費量を低減し、処理効率の改善が見込めます。 生成AIは出力したものの中身を理解してないと考えるべき 生成AIの仕組みとして、コンテキストウィンドウに蓄積した情報に乱数を混ぜつつ、蓄積している断片的な情報を組み合わせて出力を行います。 つまり、出力したものの整合性が取れているかどうかは、現状だとほぼ検証されていません。 よって、生成AIが何かを出力したら、出力されたコードなりドキュメントなりを検証してフィードバックを返す必要があるのです。 フィードバックを生成AIに返す一番明確な方法は人間が出力成果物を理解したうえで、プロンプトによって伝えることです。 しかし、これは人間のレビュー速度が生成AIが成果物を出す速度を上回る必要があり、現実的には持続可能性がありません。 加えて、生成AIが出す8割の成果物はほぼ意味のないものです。人間は意味のないものを無限にレビューし続けることはできないでしょう。 幸運なことにソフトウェア開発には、これまでの技術的蓄積としてレビューを自動化するための仕組みが多数存在します。 それが、 継続的インテグレーション です。現代的な プログラミング言語 には、必ずコードフォーマッタやLinter、 コンパイラ などの自動的にコードの不備を指摘するためのソフトウェアがそろっています。もちろん、自動化された ユニットテスト や 結合テスト 、E2Eテストも成果物の妥当性を生成AIに伝える良い手段になります。 これらを使って生成AIが出力した成果物を自動的に検証し、無意味なものを人間がレビューせずに済む環境を構築しましょう。 要件定義→外部設計→作業計画の流れを作るべき 自然言語 を使ってコンテキストを構築する以上、生成AIを使ったソフトウェア開発においても、これまで私たちが蓄積してきたノウハウはそのまま適用可能です。 Claude Codeのコンテキストウィンドウには二十万 トーク ンという明確な制限があります。これを実作業に照らし合わせて考えると、おおむね30分~60分程度で実施できる作業範囲内に収まる情報量です。人間に比べるとあまりにも少ないですね。 つまり、生成AIに対して指示を出すにあたって、作業範囲に関する知識はできる限り小さく集約されており、一貫性があり、正確で再現可能なものであることが望ましいということです。 これは、プロンプトを記述する人間に求められる最も重要な技能です。自らの指示が、どういう意図で何をやりたいのか、作業の完了や完成をどのように定義するのかを 言語化 できると、生成AIが作り出す成果の精度が向上します。 例として、直近で私がそれなりに難しいタスクとして実装したApplication Bundleに関連する実装作業について確認してみましょう。 まず、私自身が macOS 用のインストールテストを実施している際に発見した奇妙な状況をClaude Codeに共有し、それがどういうものであるのかをplan-modeで説明してもらいました。 いくつかの質問と応答の後、その状況がApplication Bundleという仕様に基づくものであり、私が奇妙だと感じたものが、実は macOS においてはきちんと仕様化されたものであることが分かります。 そうして生成AIと私が議論した結果をArchitecture Decision Record( ADR )として記録したものがこれです。 ADR-018: macOS JDK Bundle Structure Handling これによって、Application Bundleに対してKopiではどのような方向性で対応を行うのかが決まりました。 次はこれを使ってどのように実装するのか機能仕様をDesignDocとして記述してもらいます。 ここで、自分が生成AIに対して期待していることを余すことなく 言語化 します。要求の中で曖昧な部分や矛盾している部分を消し込んでいくために、出力されたDesignDocをレビューします。なお、Claude Codeは ソースコード で説明しようと試みることがありますが、これを承認すべきではありません。 macOS JDK Bundle Structure Handling Design 満足のいくDesignDocになったら、次は作業計画の立案です。 macOS JDK Bundle Structure Implementation Plan 作業計画書のフォーマットにはいくつかポイントがあります。 フェーズ分けをすること。各フェーズの切れ目では /clear コマンドによってコンテキストをクリアすることをプロンプトの中で宣言すること。 各フェーズで入力となる成果物を明記すること。 各フェーズで行うタスクの一覧は更新可能な チェックボックス で構造化し、作業状況の進捗に応じて作業計画を更新させること。 各フェーズでの作業成果物を明示するか、作業完了をどのように確認するか明記すること。 各フェーズは30分~60分程度で終わる粒度になるよう調整すること。長時間のタスクを現在の生成AIでは実施できません。 こうすることによって、生成AIが各フェーズ内において必要十分な情報をコンテキストに読み込んだうえでコードの生成を行うようになります。 作業計画の立案が終わったら、 /clear コマンドでコンテキストをクリアし、 @docs/tasks/ap-bundle/plan.md のフェーズ1を実装してください のような形で指示を出します。 出力された ソースコード を確認して、微調整をしたらフェーズの完了確認のために、自分でコミットをします。 私の場合は、生成AIに対しては基本的に読み取り以外のgit操作をさせないことにしています。それは、おおむね一発で精度の高い成果物を出してくるわけではないからです。細かい調整をしながら --amend コミットをしても良いのですが、そうするくらいなら最初からコミットさせない方が良いという判断を現在はしています。 CLAUDE.md には以下のような記載があり、ほとんどの場合では、フォーマッターやLinter、ユニットや 結合テスト を実行するのですが、コンテキストに余裕がなくなってくると、生成AIがそれらを実施しないことがあります。 ## Development Workflow ### Completing Work When finishing any coding task, always run the following commands in order and fix any issues: 1. ` cargo fmt ` - Auto-format code 2. ` cargo clippy --all-targets -- -D warnings ` - Check for linting errors in test code 3. ` cargo test --lib --quiet ` - Run unit tests (faster than full test suite) Address any errors from each command before proceeding to the next. All must pass successfully before considering the work complete. 実施していない様子が見えたときには、カスタムスラッシュコマンドとして /finish というコマンドを定義していますので、これを実行します。 https://github.com/kopi-vm/kopi/blob/v0.1.3/.claude/commands/finish.md 現時点では、生成AIの技術進歩や新しい技法の発見速度が非常に速いので、あまり多くのカスタムスラッシュコマンドを作りこんでしまわないようにしています。 ここで紹介したやり方は、 Amazon のKiroと概要レベルでは非常に似通っていますが、ある程度の規模でAI駆動開発を行おうとすると、誰もが同じやり方に到達するのでしょう。ドキュメントを読む限りでは、Kiroの方が洗練されたやり方のように感じられます。私自身はKiroをまだ試せていないのですが、今後取り入れていきたいと考えています。 まとめ このエントリでは、生成AIの一つであるClaude Codeを使って実装した、 JDK のバージョンマネージャであるKopiを紹介しました。 この約二か月間の間に起きた私の 心理的 変化について、皆さんに共有したのは今の生成AIブームがある種の熱狂を生んでいると感じているからです。個人的には、15年から20年ぶりの技術的な熱狂を感じながら生成AIを触っています。 この新しい道具が一体どこまで成長し、私たちの仕事や社会を変化させるのか、一緒に楽しんでいけるなら幸いです。 私たちは一緒に働いてくれる仲間を募集しています! 電通総研 キャリア採用サイト 電通総研 新卒採用サイト 執筆: @sato.taichi レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )