TECH PLAY

サイオステクノロジー(Tech.Lab)

サイオステクノロジー(Tech.Lab) の技術ブログ

621

生成AIの分野でもRAGは特に注目を集めており急成長している分野です。あまりに多くの情報に溢れており、各手法をどう捉えてよいか分かりにくい状況にあります。このブログではRAGがどのように成長し、どのような改善が試みられているのかを整理して、包括的で体系的に理解できるような説明を試みました。読んでいただいた方の理解の助けになれば幸いです。 RAGの概要 LLMは非常に優れた効果を発揮したものの、嘘を答えてしまうハルシネーションや古い知識での応答、回答の根拠の分からない不明瞭な推論といった問題を抱えています。RAGはこれらの課題に対し外部データベースを取り入れるというアプローチで解決を試みており、特に注目を集めている分野であり急速に進化しています。また、外部データベースを取り入れるという特性からドメイン固有の知識とLLMを組み合わせることができるため、企業からも大きな注目をされており研究・開発を実施されています。 基本的なRAGシステムのシナリオは図1のようになります。まず、ユーザがシステムに対して質問を投げかけます。システムは質問を元にデータベースに関連するドキュメントを問い合わせます。そのドキュメントを元にLLMが回答を生成しユーザに提供します。これによりユーザは従来の単純な検索システムよりもLLMを介することによって柔軟な質問でデータにアクセスしたり、的確な回答を得ることを期待できます。 (図1: 基本的なRAGの動作。 Retrieval-Augmented Generation for Large Language Models: A Survey [ 1 ]より引用) しかし、単純な方法では期待されたほどの効果は得られないことが分かってきており、現在は様々な改善が試みられているという状況です。その改善について説明していきます。 RAGの進化とパラダイム RAGのパラダイムはNaive RAG、Advanced RAGを経て、Modular RAGへと進化しています。Naive RAGは最も基本的なRAGでシンプルですが多くの課題を抱えています。Advanced RAGはNaive RAGのフローをベースとして各処理の改善したものとなっています。さらに、Modular RAGでは、RAGで必要な各処理をモジュール化し、それらを組み替えることで新たなパターンを生み出して大きな性能の改善を試みています。 Naive RAG (Baseline RAG) Naive RAG(Baseline RAGと呼ばれることもあります)は最も素朴なRAGのことです。インデクシングではPDFやWord、Markdownの文書データをプレーンテキストに直した後に固定のトークンの長さ(100, 256, 512など)でチャンクに分割。チャンクごとのベクトル表現を計算し、ベクトルデータベースに格納します。検索ではユーザの質問をベクトル表現に変換し、データベースから類似度の高いコンテンツを検索して、ドキュメントから回答を生成します。 このRAGは大きなの欠点を抱えていると指摘されています[ 1 ]。 検索の課題: 検索の再現性や精度が悪く、不揃いで無関係なチャンクがヒットし、重要な情報が欠落してしまうという。 回答生成の困難: RAGでも単にLLMを利用する場合と同様に ハルシネーションを起こすことがあり、無関係であったり、有害であったり、バイアスのある情報を出力しうる。 検索結果の統合のハードル: 検索さらた複数の結果をうまく統合できずに適切な応答ができないことがある。多くの文書から関連性や重要性を判断し、統合して、一貫性のある回答を引き出すことは困難である。これらには一度の検索処理では不十分である。 1度でもRAGを構築されたことがある方なら頷ける内容ではないかと思います。RAGは基本的にこれらの課題を解決しようと進化を続けています。 Advanced RAG Advanced RAGはNaive RAGの基本的な処理フローを踏襲しつつ、検索や回答生成など各処理の品質の向上に着目し、回答の精度の改善をしようとしています。 (図2: Naive RAGとAdvanced RAGとModular RAGの処理イメージ。 Retrieval-Augmented Generation for Large Language Models: A Survey [ 1 ]より引用) 図2のAdvanced RAGのオレンジ部分がNaive RAGに対し追加されているテクニックです。Advanced RAGのテクニックはもう少し複雑であったり他のオプションを含むものなので、この図で完全に表されている訳ではありませんが、インデックス化、検索手法、回答生成のそれぞれに対して改善しているのがAdvance RAGです。これらの改善については 改善手法 のセクションで紹介します。 Modular RAG 単純にRAGの質問、検索、回答生成という線形なプロセスの工程をそれぞれ改善するだけでは限界がみえてきました。そこで、Modular RAGはNaive RAGやAdvanced RAGを基本としつつ、RAGの機能のモジュール化により適応性と再利用性を向上しようとしています。図2のようにModular RAGではRAGの各機能がモジュールとなり、それらを組み合わせた「パターン」をちょうどLEGOブロックのように組み上げていくという方法をとります。 より具体的にModular RAGの内容を見ていきます。Modular RAGは次の3層に分かれたアーキテクチャを持ちます。 モジュールタイプ (Module Type) モジュール (Module) 演算子 (Operator) モジュールタイプは最上位のレイヤーで、RAGのインデックス作成や検索、生成といった工程を表します。モジュールは中間のレイヤーで、各工程で実施する機能でチャンク化やクエリルーティングなどのより具体的な機能を表します。最後に演算子は最下位のレイヤーで、構造化インデックスやHyDEなど具体的な実装を表します。 このような3層の抽象化を用いることで自在な組み合わせを実現します。図3はモジュール、サブモジュール、演算子の具体例です。 (図3: Modular RAGのモジュールの一覧。 Modular RAG: Transforming RAG Systems into LEGO-like Reconfigurable Frameworks [ 2 ]より引用) インデックス作成 (Indexing)、検索 (Retrieval)、検索事前処理 (Pre-Retrieval)、検索事後処理 (Post-Retrieval)、回答生成 (Generation)という従来のRAGの機能をモジュールしたもののほかに、オーケストレーション (Orchestration)が存在します。オーケストレーションはModule RAGの重要なモジュールタイプで、異なるモジュール間の調整役を担います。データやクエリに基づいて適切なモジュールを選択してフローを制御することで、より多くのデータやクエリに対して適応できるようになります。 Modular RAGはツールやフレームワークを利用することで実現することができます。Modular RAGを実現するツールやフレームワークをいくつかご紹介します。 Cognita は Langchain/LlamaIndex のラッパーのように動作し、RAGで必要なモジュールを提供し、 Langchain/LlamaIndex を単体で用いるよりも再利用性が高まります。 Advanced RAGが構築可能な Haystack と軽量なAI/機械学習の構成管理フレームワークである Hypster を組み合わせることで実装することもできます[ 3 ]。 AutoRAG はモジュールの組み合わせを自動的に選択できるという特徴を持つツールです[ 4 ]。G-EvalやRAGAsといった評価ツールを用いて、自動評価を実施し適切なパターンを走査してくれます。 まだ、この領域は発展途上であり成熟したツールは存在していませんが、注目度が高く変化が激しい状況です。今後さらに発展する領域と考えられ注視していく必要があります。 RAGの進化の過程 ここまでみたようにRAGはNaive RAG、Advanced RAG、Modular RAGという変遷を辿っています。これらのパラダイムは全く別ものではなく、前のパラダイムを拡張・改善する形で進んできています。Advanced RAGはNaive RAGの各処理をより洗練させ、Modular RAGはAdvanced RAGの処理をモジュール化し、その組み合わせのパターンによるより柔軟なアーキテクチャで非常に大きな工夫の余地を与えてくれています。また、最新のModular RAGというパラダイムが出たので、Advanced RAGは終わったかというとそういう訳ではなく、Advanced RAGにおける改善手法は現在でも進化を続けており、それがModular RAGのモジュールとなり進化していくという流れは続いています。そういう意味で今後しばらくの間は特定のパラダイムだけでなく全体を追っていく必要があります。 RAGとファインチューニング RAGとファインチューニング(追加学習)は類似した技術であるためしばしば比較されます。ここではRAGとファインチューニングの関連について説明します。 RAGとファインチューニングの違い RAGとファインチューニングはどちらもモデルがもともと持っていない情報をモデルに取り扱わせるという意味で同じですが、両者の方法はそれぞれことなります。RAGは前述の通りで外部のデータベースから情報を参照し、その情報を元に回答を生成します。通常、RAGでは事前学習されたモデルをそのまま使います。一方で、ファインチューニングはモデル自体にさらに追加の学習を行わせるという方法で、モデル自体(LLMのニューラルネットワークのパラメータ自体)が更新されて新しい情報を取り込みます。ファインチューニングは質問とそれに対する回答のセットである「学習データ」を用いてモデルを調整します。OpenAIなどのプラットフォームではこのような学習データをアップロードするだけで、ファインチューニングできる仕組みが提供されています[ 5 ]。 ファインチューニングは特定用途のタスクに対して効果的な能力を発揮します。例えば、特定ドメインの思考方法やより厳格な推論能力を向上などに効果が発揮されています[ 7 ]。しかし、多くの学習を行うと以前に学習したことを忘れてしまうといった「破壊的忘却」をしたり、学習した情報を回答できるようになってもその根拠を示せなかったり、新しいデータが更新されるたびにファインチューニングの手間がかかるといった課題があります。 RAGはファインチューニングがブラックボックス化してしまうのに対し、インデックス作成や検索・回答生成などの段階が見える形で存在しているためカスタマイズしやすかったり、データベースを更新すること情報が更新できるので最新を保ちやすいという特徴があります。しかし、RAGの回答は検索の品質に大きく依存しているため、RAGの構築は検索の工夫がどれだけできるかにかかっているとも言えます。 RAGとファインチューニングの性能 Microsoft Researchによる論文ではファインチューニングに対して、RAGの方が性能が高いと報告されています[ 6 ]。 (図4: Oren Elisha, Fine-Tuning or Retrieval? Comparing Knowledge Injection in LLMs [ 6 ]より引用。) この検証ではRAG単体が最も性能が良く、次にRAGとファインチューニングの組み合わせ、最後にファインチューニングという順で性能が高いという結果になりました。Llama2に関してはファインチューニングとRAGの組み合わせが最も高い結果となりました。したがって、検索という観点ではRAGが優れているでしょう。 RAGとファインチューニングの融合 検索ではRAGが優れていたからといってRAGだけを用いるだけでなく、RAGとファインチューニングは両方を活かすことができます。 前セクションの論文では知識を正しく取り出せるかに着目しており、それ以外のタスクに関する検証は行なっていないので、単純にファインチューニングがRAGより一般的に劣っていると判断する材料にはなりません。例えば、専門分野における推論能力の向上では専門分野のコーパスを用いてファインチューニングさせることで性能が向上されたことが報告されています[ 7 ]。このようなタスクに関しては原理的にRAGで対応することは難しいでしょう。 つまり、図5のように外部知識が必要となるタスク(External Knowledge Required)ではRAGが有効で、モデル適応が必要となるタスク(Model Adaptation Required)ではファインチューニングがより有効です。Moduler RAGではファインチューニングされたモデルを組み合わされる方法が使われてきています。 (図5: Retrieval-Augmented Generation for Large Language Models: A Survey [ 1 ]より引用) 改善手法 RAGで提案されている改善を次の3つに分けて紹介していきます。 インデックス 検索 回答生成 インデックス チャンク化戦略 チャンク手法はRAGに取り込む大きな文書の塊を小さく分割する手法です。分割されたチャンクごとにインデックスが作成されるため、チャンクは検索の品質に大きな影響を与えます。ここの内容は 「 Chunking Strategies for LLM Applications 」[ 8 ]の内容を参考に説明します。 固定サイズチャンク 最も単純でNaive RAGで用いる方法が固定のチャンクサイズで機械的に分割する手法です。固定サイズのチャンクではチャンクサイズにはトークン数が用いられます。トークンとはTokenizerによって分割される単位です。通常は単語数や文字数ではないです。どのようにトークン分割されるかは Tokenizer – OpenAI Platform などを使って試すことができます。また、単純に固定のチャンクサイズで切ってしまうと文章の前後の繋がりの情報が失われてしまうため、隣り合ったチャンクで同じトークンを重複してもつ「オーバーラップ」を設ける必要があります。 この方法は自然言語処理(NLP)を必要としないため、計算量が小さいというメリットがありますが、全く文章の意味が考慮されないため、チャンクの質は悪くなりがちというデメリットがあります。 コンテンツ認識チャンク 固定サイズのチャンクは機械的で意味が考慮されないという点で、チャンクの質が悪いという問題点がありました。それに対し、コンテンツをきちんと認識した上で、より有効なチャンク化しようという方法もあります。その方法をいくつか紹介していきます。 (表1: 5つのコンテンツ認識チャンク。下の行ほど高度に意味を認識して分割するチャンク化方式。) チャンク方法 説明 文の分割 (Sentence splitting) 区切り文字などを利用して文に分割してチャンクとして扱う非常に簡単な方法です。実装は単に「。」などの句点で分けるコードを自作する方法 (Naive splitting) があります。だた、そのような方法では例外的なパターンなどでは分離できないので、NLTKやspaCyなどのPythonのライブラリによって文を分割という方法も用いられます。ライブラリを用いることでより適切に文を分離することができます。 再帰的チャンク化 (Recursive Chunking) 大きな文章をいくつかの文書に分割し、さらにその文書を分割するという処理を繰り返し、階層的かつ反復的に小さいチャンクに分割する方法です。再帰は指定されたチャンクサイズになるまで繰り返されます。分割には複数の区切り文字を使用します。再帰が繰り返される中で使用される区切り文字が変わり、目的のチャンクサイズになるように処理されます。実装はLangChainのRecursiveCharacterTextSplitterを用いる方法があります。 ドキュメント固有のチャンク化 (Specialized chunking) MarkdownやLaTexなどといった文書は構造化されていて、見出し、リスト、コードブロックなどの情報を用いることで、より正確にコンテキストを理解したチャンクを得ることができます。実装はLangChainのMarkdownTextSplitterやLatexTextSplitterを用いる方法があります。その他のドキュメントでも形式が明確なものであれば正規表現等を用いて、Splitterを自作することも可能でしょう。 意味的チャンク化 (Semantic Chunking) Greg Kamradtによって導入された手法です。その名の通りで文章の意味によってチャンクに分解します。文章を文に分割し、文の意味が近いものを同じトピック、遠いものがあれば別トピックに切り替わったと判断して、チャンクに分解します。上記の文章の表面的な形式で分割していたのに対し、より踏み込んだチャンク化です。実装はLangChainのSemanticChunkerを用いる方法があります。 エージェントによるチャンク化 (Agentic Splitting) さらに先進的な手法として実験的ではありますが、AIエージェントによる分割も提案されています[ 10 ]。 ベクトル化 分割されたチャンクは埋め込みモデル (Embedding Model) によってベクトル化されます。モデルの選定やチューニングについてご紹介します。 埋め込みモデルの選定 ベクトル化では意味の近いベクトルは類似度が近く、意味が遠いものは類似度が遠くなる分類性能の高いベクトル化されることが望ましいです。そのため、埋め込みモデルの選定はインデックスの品質に影響します。多くの場合では、OpenAI社の ada v2などのモデルを利用することが多いですが、 モデルの選択はリーダーボードを確認し、指標を比較して独自にモデルを選ぶことができます。リーダーボードでは多くのモデルに対しベンチマークを実施してその結果が公開されています。例えば、有名なリーダーボードとしては MTEB Leaderboard や Nejumi Leaderboard などがあります。特にNejumi Leaderboardは日本語での性能を評価したもので、日本語を扱う際のモデル選定では参考になります。 埋め込みモデルのファインチューニング 医療や法律など専門用語などは一般的な語用とは異なる場合があり、意図したベクトル化がされないことがあります。そのような場合には埋め込みモデルのファインチューニングが有効なケースがあります。 LLamaIndex などではファインチューニングが可能です。 構造インデックス インデックスの構造によっても検索の精度が変わります。ここではいくつかのインデックスの方法について「 Advanced rag techniques: an illustrated overview 」[ 9 ]を中心にご紹介します。 ベクトルストアインデックス (Vector store index) 最も単純に質問と回答のベクトルの類似度の近いものを検索する方法です。Naive RAGで使われる最初のアイデアです。ベクトル類似度と近傍探索のアルゴリズムは複数あります。 まず、ベクトルの類似度の計算についてElasticSearchでよく次の演算が使われています[ 11 ]。これらはElasticSearch固有の概念ではなく、数学や物理、情報工学で広く使われています。 マンハッタン距離 (L1距離) ユークリッド距離 (L2距離) コサイン類似度 ドット積 (内積、点乗積) 意味ベクトルは方向によって意味が表される性質があるので、RAGの文脈ではコサイン類似度やドット積といった「点」ではなく「ベクトル」として扱われる演算が相性がよく比較的精度がよいといえます。また、コサイン類似度はベクトルの角度のみが考慮されますが、ドット積はベクトルの長さも考慮されるのでより多くの情報を持ちます。しかし、情報が多ければよいというわけでもなく、長さがノイズになる場合もあるため比較して検討が必要となります。 次に近傍探索についてみていきます。近傍探索とは周辺にあるベクトルを探すためのアルゴリズムです。その方法には主に次の2つが存在します。 k最近傍法 ( k-Nearest Neighbor: kNN ) 近似最近傍法 (Approximate Nearest Neighbor: ANN) kNNはすべてのデータを比較して近いベクトルを見つけ出すアルゴリズムで、正確にベクトルを見つけ出すことができます。しかし、単純に全てのベクトル同士の距離を比較してしまうと、すべてのデータを探索することになり検索に非常に大きな時間がかかってしまうことになります。ちょうどリレーショナルデータベースでインデックスのないカラムに線形検索するようなものです。そのため、データベースとしては小規模〜中規模程度のデータベース向けで、大規模の場合にはRAGの応答性を犠牲にしても正確性を求める場合に利用します。 ANNは多少正確さを犠牲にして素早く近傍のベクトルを見つけるためのアルゴリズム全体の総称です。現在はその主流のアルゴリズムとしてHierarchical Navigable Small World (HNSW)が使われています。HNSWのアルゴリズムを簡単に説明します。図6のようにベクトルの近さをグラフとして表現し、グラフの詳細度で複数のグラフを構築します。この際に各グラフに対応するノードを設けます。このグラフを粗い方からたどり、クエリのベクトルに近いノードを探します。次にそのノードに対応する1段詳細度の高いグラフに移動し、もう一度探索します。最後は実際のベクトルと対応したグラフに移動し、最も近い(可能性が高い)ノードを発見します。イメージ的には札幌・仙台・東京・名古屋・大阪・福岡くらいしか書いていない広域の地図から場所を探して、次に関東周辺の地図から神奈川あたりが近そうだと分かり、神奈川県の地図から横浜みなとみらいを探し当てる感じといったところかと思います。 (図6: Hierarchical Navigable Small Worlds (HNSW) [ 12 ]より引用) HNSWでもかなり正確な結果が得られることが一般に知られています。そのため、アルゴリズム選択にあたってはANN近傍探索はまずANNから検討を始め、どうしてもそれでは検索精度が悪い場合にはkNNを選択するという進め方がよいでしょう。 多くのデータベースでは計算方法や近傍探索アルゴリズムを変更できるパラメータが用意されています。サポートされている計算方法・近傍探索はデータベースにより異なりますので、ドキュメントをご確認ください。 チャンクのメタデータ チャンクにページ番号やファイル名、カテゴリ、タイムスタンプなどのメタデータを付与する方法があります。チャンク生成時にデータベースにこれらのメタ情報を一緒に保存するように実装します。このメタデータで検索範囲の絞り込みをしたり、タイムスタンプでの絞り込みで古い情報を排除するなどして検索の精度を高めることができます[ 1 ]。 階層的インデックス (Hierarchical indices) (図7: Advanced rag techniques: an illustrated overview [ 9 ]より引用) 大規模なドキュメントを検索する際に細切れの小さなチャンクでは適切にコンテキストを保持できないという欠点がありました。そこで階層的インデックスのアプローチでは図7のように要約のチャンクに対して検索を実施し、さらに要約に関連づけられたより小さなチャンクを検索するという方法をとります。階層は要約の単位を変えることで複数に増やすことができます。これにより関連性の高いドキュメントを発見したり、要約の階層のドキュメントを回答に使うことができるなどのメリットがあります[ 13 ]。 実装についてはNirDiamant氏により参考となる Hierarchical Indices in Document Retrieval が公開されています。 想定質問(Hypothetical Questions) Naive RAGではユーザからのクエリをベクトル化することで検索を行いますが、質問のベクトルから内容のベクトルを検索するという動きになり、それらは必ずしもベクトルが類似するとは限りません。そこで、質問のベクトルから質問を検索したり、回答のベクトルから回答を検索するといった方法が提案されました。 想定質問(またはReverse HyDE)は質問のベクトルから質問を検索する方法で、インデックスの作成時点で内容に対する想定質問をLLMに生成させ、その質問をベクトル化したものをデータベースに格納します。例えば、「富士山の標高は3,776mです」というチャンクに対して、その文章そのもののベクトルでなく、LLMで質問「富士山の標高は?」という想定質問を生成します。この文章に対するベクトルをベクトルストアに保管します。するとユーザが質問した「富士山の高さはどのくらい?」との類似度が高くなる可能性が高く精度が高くなると予想できます。 Reverse HyDEと呼ばれている理由はHyDE(後述)が質問に対する「想定回答」で検索するというアプローチをとっており、その逆のアプローチであることからそのように呼ばれています。 周辺コンテキスト拡充 (Context enrichment) インデックス作成時にチャンクに周辺の文章を付与してLLMに推論させる方法です。LLMは検索したチャンクだけでなく周辺の文章を用いることができるので、文脈をよりよく理解して回答を生成することが期待できます。センテンスウィンドウ検索と自動マージ検索の2つの方法があります。 センテンスウィンドウ検索 (Sentence Window Retrieval)は単純に検索されたチャンクの周辺の文章をLLMに送る方法です。下図の緑の部分が検索で引っかかったチャンクであり、拡大して黒の文章を付与します。 (図8: Advanced rag techniques: an illustrated overview [ 9 ]より引用) 自動マージ検索 (Auto-merging Retriever (別名 Parent Document Retriever))はチャンクを親と子に分けて検索する方法です。まずは通常通り小さいチャンク(これを「子チャンク」という)に対して検索を行い、ヒットした子チャンクの属する親のチャンクを取得し、より大きな親チャンクの方をLLMに送る手法です。 (図9: 自動マージ検索のイメージ。 Advanced rag techniques: an illustrated overview [ 9 ]より引用) 具体的な実装については例えばLlamaIndexを使った方法で Recursive Retriever + Node References を参照ください。 ナレッジグラフ (Knowledge Graph) これまでのところはクエリに対していかに近い文章を取得したり、いかに周辺の文章も含んでコンテキストを保持するかがメインのテクニックでした。しかし、これだけでは不十分でクエリとは直接関連しなくとも、検索された文書とは関連して回答したい内容については取得できず、十分な回答が生成されなかったり、ハルシネーションを起こすという欠点がありました。 ナレッジグラフはそのような欠点を解決するために知識と知識を関連付けたグラフ型のデータベースを構築し、ベクトルストアデータベースよりも関連した情報を包括的に検索できるように考えられています。 ナレッジグラフの動作について簡単に説明します。ナレッジグラフとは図10のようなノード(丸)とエッジ(線)からなっており、ノードで対象となるオブジェクトを表し、エッジでそれらの関係性を表すグラフとなっています。RAGではノードに対してチャンクを割り当て、チャンク間の関連をエッジで表します。これによりベクトルストアインデックスで表現できなかった関連性を表現することができます。 (図10: LangChainのLLMGraphTransformerで構築したナレッジグラフをNeo4jで表示した例。 Enhancing RAG-based application accuracy by constructing and leveraging knowledge graphs [ 14 ]より引用) 検索は多くの場合ベクトルストアインデックスと併用で実装されるケースが多いです。手順としてはまず質問に対してベクトルストアからチャンクを取得し、そのチャンクに関連する情報をナレッジグラフから検索するという方法です。 (図11: ベクトルストアインデックスとナレッジグラフの併用。 Enhancing RAG-based application accuracy by constructing and leveraging knowledge graphs [ 14 ]より引用) 実装についてはLangChainのLLMGraphTransformerを用いる他に、現在注目されているのが、Microsoft Researchにより提案されている GraphRAG です。Graph RAGは2段階のナレッジグラフを用いて、部分的な知識の要約を1段階目で生成し、2段階目でその要約全体を用いて質問するため、従来手法より回答精度が高いとされています[ 15 ]。 検索 複合検索とハイブリット検索 (Fusion retrieval or hybrid search) 複数の検索手法を用いることで検索精度を向上させようとする方法は多く用いられています。基本的にはベクトル検索がベースとしてもちいられ、ベクトル検索の弱点を補う形で用いられることが多いです。特に代表的なものをご紹介します。 キーワード検索 ハイブリッド検索では多くの場合、ベクトル検索とキーワード検索の両方を用いることをいいます。キーワード検索とは単語や語句から文書を検索することを言います。RAGでは質問に含まれる単語や語句を自然言語処理によって抜き出してきて、一致したキーワードを含む文書を見つけ出します。 これはベクトル検索が意味として近い文章を見つけ出すのが得意なものの、同じキーワードが含まれていたとしてもベクトルの類似度が低ければ検索にヒットしないという弱点を克服するために用いられる方法です。 セマンティック検索 セマンティック検索は語句や単語の「意味」を基に検索をする検索方法です。例えば、「携帯」という単語から「ケータイ」「電話」や「iPhone」など似た意味の単語もヒットします。 これもキーワード検索との併用の効果と同じく、類似度が低くても近い意味合いの単語が使われているチャンクを取得することができます。 タグ検索 前述の チャンクのメタデータ で紹介した通り、あらかじめチャンクにメタデータを付与しておくことで、データを絞り込み検索精度を向上することができます。 グラフ検索 前述の ナレッジグラフ で紹介したとおり、ナレッジグラフを用いることで関連する文書より包括的に取得できるようになり、検索精度を向上することができます。 クエリ ユーザから提示された質問は必ずしも検索に対して最適とは限らず、クエリに対し何らかの工夫を施した方が検索精度が上がるといわれています。 クエリ拡張 単一のクエリだけを用いるのではなく、複数のクエリを使ったり、1つのクエリをより簡単なクエリに分割する検索の精度を上げる手法です。クエリ拡張には次の種類があります。 マルチクエリ (Multi-Query) サブクエリ (Sub-Query) マルチクエリ (Multi-Query)はユーザからのクエリを別の観点を取り入れて複数のクエリを生成する手法です。これにより単一のクエリだけでは取得できなかった結果を取得することができます。ユーザからのクエリの微妙なニュアンスの違いでベクトル検索にヒットしないような内容も取得できるといった効果が期待できます。実装では MultiQueryRetriever を使う方法があります。実際にMultiQueryRetrieverを使ってクエリを生成すると次のようになります。 元のクエリ: What are the approaches to Task Decomposition? (和訳: タスク分解へのアプローチは何ですか?) 質問1: How can Task Decomposition be achieved through different methods? (和訳: さまざまな方法でタスク分解を実現するにはどうすればよいでしょうか?) 質問2: What strategies are commonly used for Task Decomposition? (翻訳: タスク分解に一般的に使用される戦略は何ですか?) 質問3: What are the various ways to break down tasks in Task Decomposition? (和訳: タスク分解でタスクを細分化するさまざまな方法は何ですか? ) ( MultiQueryRetriever より引用・改変) このようにさまざまな聞き方の質問に変換されていることがわかります。これにより多くの情報を得られる可能性が高まります。 サブクエリ (Sub-Query)はユーザからのクエリを複数のクエリにより簡単なクエリに分割して検索する手法です。ユーザからの質問は複数の質問が関連したものであることがあり、そのようなケースでは一度のクエリでは解決できず、単純な質問に分割して検索することで的確な情報を取得できる効果が期待できます。実装はプロンプトエンジニアリングを使用して、質問を分割するようにLLMに指示します。実装例は Part 7: Decomposition – Rag From Scratch: Query Transformations に示されていますので参考としてください。実際にクエリを生成すると次のようになります。 元のクエリ: how to use multi-modal models in a chain and turn chain into a rest api (和訳: チェーン内でマルチモーダル モデルを使用し、チェーンを REST API に変換する方法) 質問1: How to use multi-modal models in a chain?  (和訳: チェーン内でマルチモーダル モデルを使用するにはどうすればよいでしょうか?) 質問2: How to turn a chain into a REST API? (和訳: チェーンを REST API に変換するにはどうすればいいですか?) ( Decomposition より引用・改変) このように複数のトピックが含まれているような質問が分割されていることがわかります。 クエリ変換 クエリ変換は元のクエリを変換することで、検索の精度を上げる手法です。よくある方法としては次があります。 HyDE Step Back HyDEは回答のベクトルから回答を検索する方法で、質問に対する回答を検索なしでLLMに作成させ、その質問をベクトル化したものでデータベースから検索します。検索なしで回答させるという点について不思議に思われるかもしれませんが、これは検索しなくともニュアンスの似た回答が得られるはずだという仮説に基づきます。例えば、元のクエリが「日本で一番高い山は何ですか?」であった場合に検索を用いずにLLMから「日本で一番高い山は高尾山です」という仮の回答を得ます。これは当然ハルシネーションですが、チャンクに「日本で一番高い山は富士山です。」という文章が含まれていれば、仮の回答とチャンクのベクトルは近いはずです。実際にBM25やContrieverと比較して精度が高いことが報告されています[ 16 ]。実装については Part 9: HyDE – Rag From Scratch: Query Transformations に示されていますので参考としてください。 Step Backは元のクエリをより抽象的な質問にLLMで変換させ、元のクエリと抽象的な質問の両方を使って検索を実施する手法です。これにより主に複数の情報源から結果を導き出すマルチホップ推論が必要な場面での精度が向上されたと報告されています[ 17 ]。実装については Part 8: Step Back – Rag From Scratch: Query Transformations に示されていますので参考としてください。 ルーティング 検索は1回だけではなく、複数回実施するのも有効な手段です。 (図12: 反復検索と再帰検索と適応検索のフローチャート。 Retrieval-Augmented Generation for Large Language Models: A Survey  [ 1 ]より引用。) 単一検索 Naive RAGで使われる単純な検索方法で、一度の検索で得られた結果を用いて回答を生成する方法です。シンプルな手法で応答性も高いものの、一度の検索では適切な回答を得られないことが多いという課題があります。 反復検索 反復検索は以前の検索結果を用いて、関連する文書を繰り返し取得する方法です。これにより周辺のコンテキストを補うことが期待できます。ただし、ノイズとなる情報が増えてしまうこともあります。 再帰検索 再帰検索は以前の検索結果を用いて、Chain-of-Thought (CoT) を用いてクエリを改善し、繰り返し検索をする方法です。この方法では検索結果のフィードバックを受けて、徐々に関連性の高い文書を探し当てることができます。 適応検索(AIエージェント) 適応検索とはLLMが次の検索をどのようにするかの次のアクション自体を考えさせるという方法です。最近特に注目を集めているのは適応検索の領域です。Flare、Self-RAG、AutoRAG、Toolformer、 Graph-Toolformerなどのツールが提案されていますが、ここでは より注目度の高いAIエージェントを用いたマルチドキュメントエージェント型の手法について簡単に説明します。   (図13: マルチエージェント型のアーキテクチャ例。 Advanced rag techniques: an illustrated overview [9]より引用) 図13のようにエージェントは担当するインデックスごとのドキュメントエージェントが配置され、ドキュメントエージェントを総括するトップエージェントの構造からなります。ドキュメントエージェントはトップエージェントに指示に合わせて、ベクトルストアインデックスと要約のインデックスなどデータベースへの問い合わせを自立して判断し実施します。トップエージェントはクエリをどのように分割して、各ドキュメントエージェントに引き渡したり、回答を統合したりについて判断します。これにより異なるドキュメントからの情報を統合した高度で洗練された回答得ることが可能になります。しかし、この手法はエージェント間の多段で複雑なクエリと回答の段階を踏むため、応答に時間がかかるというデメリットもあります。実装はLlamaIndexの Multi-Document Agents を用いることができます。 回答生成 再順位付けとフィルタリング 検索では必ずしも関連性の高い文書だけがヒットするわけではなく、関連性が薄くノイズとなるものも混在します。検索で得られた結果を改めて関連性の高い順番に並べ替えたり、フィルタリングするという方法がよくとられます。LlamaIndexの調査では再順位付けを実施することで精度が高くなるといわれています[ 18 ]。 LLMのファインチューニング RAGとファインチューニングの有効 で述べた通り、単純な知識の検索ではRAGが優れるものの、RAGとファインチューニングは両方を用いることができます。GPT-3.5-Turboをファインチューニングし、RAGAsでの評価を行った結果、回答の関連性や忠実性が向上したと報告されています[ 19 ]。 レスポンスシンセサイザー 最終的な応答生成は複数のチャンクを用いて回答を生成することになります。要約だけを示すような回答に調整したり、詳細まで説明を加えたり、精度を少々犠牲にしてLLMのトークン消費を節約するなどのケースが考えられます。LlamaIndexでは Response Synthesizer という機能を提供しており、その中で回答生成方法のオプションをいくつか用意されています。 まとめ RAGのパラダイムはNaive RAG、Advanced RAG、Modular RAGに進化している。 Advanced RAGではインデックス化、検索、回答生成のそれぞれに改善が提案されている。 Modular RAGでは多くの手法をモジュール化し、組み合わせパターンを柔軟に作り出す。 最後までお読みいただきありがとうございます。お気付きの点などありましたら、ぜひコメントください。 【宣伝】生成AI ネクストテックソリューションのご紹介 弊社サイオステクノロジーでは生成AIを用いたトレーニング、コンサルティング、アプリケーション開発のソリューションを提供しています。ご興味のある方はぜひお問い合わせください! https://nextech-solutions.sios.jp/genai/ 参考文献 Yunfan Gao, Yun Xiong, Xinyu Gao, Kangxiang Jia, Jinliu Pan, Yuxi Bi, Yi Dai, Jiawei Sun, Meng Wang, Haofen Wang, Retrieval-Augmented Generation for Large Language Models: A Survey , 2024. Yunfan Gao, Yun Xiong, Meng Wang, Haofen Wang, Modular RAG: Transforming RAG Systems into LEGO-like Reconfigurable Frameworks , 2024. Gilad Rubin, Implementing “Modular RAG” with Haystack and Hypster , 2024. Dongkyu Kim, Byoungwook Kim, Donggeon Han, Matouš Eibich, AutoRAG: Automated Framework for optimization of Retrieval Augmented Generation Pipeline , 2024. OpenAI, Fine-tuning Fine-tune models for better results and efficiency . Oded Ovadia, Menachem Brief, Moshik Mishaeli, Oren Elisha, Fine-Tuning or Retrieval? Comparing Knowledge Injection in LLMs , 2024. 森下皓文, 山口 篤季, 森尾 学 , 今一 修 , 十河 泰弘, 日立製作所 研究開発グループ, シェフィールド大学, 帰納的に多様な巨大論理推論コーパスによりLLMの汎用論理推論能力を向上させる , 2024. Roie Schwaber-Cohen, Chunking Strategies for LLM Applications , 2023. IVAN ILIN , Advanced rag techniques: an illustrated overview ,  2023. Greg Kamradt, 5 Levels Of Text Splitting , 2024. Valentin Crettaz, Vector similarity techniques and scoring , 2024. Pinecone, Hierarchical Navigable Small Worlds (HNSW) . Nirdiamant, Hierarchical Indices: Enhancing RAG Systems , 2024. LangChain, Enhancing RAG-based application accuracy by constructing and leveraging knowledge graphs , 2024. Darren Edge, Ha Trinh, Newman Cheng, Joshua Bradley, Alex Chao, Apurva Mody, Steven Truitt, Jonathan Larson, From Local to Global: A Graph RAG Approach to Query-Focused Summarization , 2024. Luyu Gao, Xueguang Ma, Jimmy Lin, Jamie Callan, Precise Zero-Shot Dense Retrieval without Relevance Labels , 2022. Huaixiu Steven Zheng, Swaroop Mishra, Xinyun Chen, Heng-Tze Cheng, Ed H. Chi, Quoc V Le, Denny Zhou, Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models , 2024. Ravi Theja, Boosting RAG: Picking the Best Embedding & Reranker models , 2023. LlamaIndex, Fine Tuning GPT-3.5-Turbo . ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post RAGはどのように進化しているのか?RAGのパラダイムと改善手法を体系的にご紹介! first appeared on SIOS Tech. Lab .
はじめに CPチームの木下です。 今回はKubernetes(K8s)の運用は複雑で、クラスタ管理に多くの知識と労力が必要となりますが、そのKubernetes管理を大幅に簡素化するOSSのRancherについて記載していきます。 Rancherはマルチクラウドやハイブリッドクラウド環境における効率的な運用を支援するプラットフォームです。本記事では、Rancherの特徴やメリットについて分かりやすく解説します。 Rancherの主な特徴について   1. マルチクラスタ管理 Rancherは、複数のKubernetesクラスタを一元管理できるOSSです。オンプレミスのKubernetesから、Amazon EKS、Azure AKS、Google GKEといったクラウドのマネージドKubernetesまで、さまざまな環境のクラスタを一つの管理画面で操作できます。 メリット: 運用者は、分散したクラスタを一元的に監視・管理できるため、オペレーションの効率が向上し、複数のKubernetes環境を持つ運用者や企業に最適です。 ユースケース: マルチクラウド戦略を採用している企業や、オンプレとクラウドを組み合わせたハイブリッド環境の運用を行う企業・プロジェクトに適しています。 2. 直感的なユーザーインターフェース Rancherのダッシュボードは、Kubernetesを扱ったことがない人でも分かりやすく設計されています。ワンクリックでクラスタの作成や管理、リソースのデプロイが可能です。 ビジュアル化された管理画面: クラスタの状態、ノードやポッドのリソース使用量、ログなどを簡単に可視化できます。 初心者でも使いやすい: GUIベースで提供されるRancherの操作画面は、Kubernetesの専門知識が少ない人でも利用しやすく、学習コストを大幅に削減できます。 3. RBAC(Role-Based Access Control)機能 Rancherは、KubernetesのRBACをさらに強化し、ユーザーごとに細かい権限設定が可能です。開発者や運用者に応じたアクセス権限の付与や、部署ごとのリソース制限など、きめ細やかなセキュリティ管理が行えます。 メリット: セキュリティポリシーに沿ったアクセス管理が可能で、コンプライアンス要件にも対応できます。 ユースケース: 大規模な開発チームや、セキュリティが厳しい業界でのKubernetes運用に最適です。 4. CICDと統合しやすい Rancherは、CI/CDツール(例:Jenkins、GitLab CI、ArgoCDなど)との統合が容易です。デプロイメントの自動化や、アプリケーションのライフサイクル管理を効率化できます。 開発者にとってのメリット: 新しいコードのデプロイがスムーズになり、継続的なデリバリーが実現します。 ユースケース: DevOps環境の整備を目指す企業や、継続的なデプロイメントが求められるプロジェクトに適しています。 5. 高可用性とスケーラビリティ Rancherは、高可用性構成が可能で、障害が発生しても運用を続けられる設計です。Rancher自体を複数のノードで構成することで、障害に強く、スケールアウトに対応します。 メリット: クラスタのダウンタイムを最小限に抑えつつ、リソースのスケールを容易に実現できます。 ユースケース: ミッションクリティカルなアプリケーションを運用する企業にとって、信頼性の高いプラットフォームとして役立ちます。 6. プラグインとカスタマイズ性 Rancherは、さまざまなプラグインやカスタム設定をサポートし、個々のニーズに合わせた柔軟な運用が可能です。HelmチャートやYAMLファイルを用いたカスタマイズも簡単に行えます。 メリット: Kubernetesエコシステムとの互換性が高く、ユーザーのニーズに合わせて拡張可能です。 ユースケース: 特定のワークロードやビジネス要件に応じたカスタマイズが必要な場合に適しています。   Rancher導入のメリット オペレーションの効率化: マルチクラウドやハイブリッドクラウド環境での一元管理により、オペレーションの手間を大幅に軽減。 セキュリティの向上: RBAC機能やセキュリティポリシーの統合で、安全なクラスタ運用が可能。 コスト削減: 管理工数の削減と最適化されたリソース管理により、インフラコストを効率的に削減。   まとめ Rancherは、Kubernetesクラスタを簡単に管理できるだけでなく、マルチクラウド対応、直感的なUI、強化されたセキュリティ機能など、商用環境でのKubernetes運用を最適化する数々のメリットを提供します。Kubernetesの複雑さに悩む運用者にとって、Rancherは心強いツールとなるはずです。 また、OSSのRancherを商用環境で導入するための、エンタープライズサポートなどもSUSE社などから提供されています。OSS単体での導入は難しい場合などでもこれらのサポートを基に検討してみてはいかがでしょうか。 今後も数回に渡りRancherに関する技術ブログを執筆予定です。 今回は初回のため概要レベルからの特徴を説明しましたが、次回以降は実際の構築時のポイントやマルチクラウドでの利用ポイントをまとめていきますので是非見ていただければ幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Rancher入門:マルチクラウド対応のKubernetes管理ツールとは? first appeared on SIOS Tech. Lab .
はじめに こんにちはサイオステクノロジーの小野です。 前回 はOpenShiftでGPUを利用するための設定方法について解説しました。今回はいよいよOpenShift AIを導入してJupyterNotebookを起動するところまでを解説します。 OpenShift AIの4つの主な機能 OpenShift AIのトップ画面は以下のようになります。 OpenShift AIのトップ画面 OpenShift AIには大きく4つの機能があります。 Data Science Projects データサイエンスのプロジェクトを作成することができます。プロジェクト内ではワークベンチの作成、パイプラインの作成(MLOps)、モデルのデプロイ、ストレージの作成、データ接続の設定、権限設定が行えます。 Data Science Pipelines パイプラインの管理、設定を行います。ここでMLOpsにおけるデータの収集からモデルの評価までを自動化できます。 Distributed Workload Metrics 分散ワークロードの管理、設定を行います。分散ワークロードとはモデルの学習や推論を行う際に、複数のマシンやクラスターに分散して処理を行うことです。これによって大規模なモデルでも高速な処理が可能になります。 Model Serving モデルのサービングの管理、設定を行います。推論用にモデルをデプロイ、​管理、​公開し、​未知のデータに対して推論サービスとして使用できるようにします。 OpenShift AI構築方法 前提条件 バージョン OpenShift:4.17 OpenShift AI:2.13.1 構築に必要なもの 1 リソース ノード数:2つ CPU:8CPU RAM:32GiB 今回はAWSのインスタンスタイプm6a.2xlargeを2つ使用 サブスクリプション Red Hat OpenShift AI Self-Managed のサブスクリプション ※検証ではRed Hat Developer Subscriptionを利用可能 機能に応じて利用するもの GPUノード(AIの学習や推論に使用) IDP設定(セキュリティ管理に使用) S3互換のオブジェクトストレージ(モデルの保存やパイプラインの作成に使用) モデルサービング用のOperator Red Hat OpenShift Service Mesh Red Hat OpenShift Serverless 導入手順 OpenShift AI オペレータインストール 最初にOperator > OperatorHubで「OpenShift AI」と検索します。 OperatorHubで「OpenShift AI」と検索 「Red Hat OpenShift AI」を選択してインストールを押します。 「Red Hat OpenShift AI」のインストールを押す インストール時の設定はデフォルトと同じく以下のように設定します。設定したらインストールを押します。 更新チャネル stable バージョン 2.13.1 インストールモード クラスターのすべてのnamespace インストール済みのnamespace Operator推奨のnamespace:redhat-ods-operator 更新の承認 自動 インストールの設定を行う インストール済みのOperatorでRed Hat OpenShift AIのステータスがSuccessedになっていたらインストール完了です。 Red Hat OpenShift AIのインストール確認 CR(DataScienceCluster)設定 OpenShift AIオペレータの詳細画面に移動し、「DataScienceCluster」のインスタンスを作成します。 DataScienceClusterの作成 公式ドキュメント 2 に従い、必要なコンポーネントをManagedに設定します。 コンポーネントは以下の10種類あります。 codeflare 分散ワークロードに利用されます。rayがManagedになっている必要があります。 kserve 大規模モデルのサービングに利用されます。OpenShift Service MeshとOpenShift Serverlessをインストールしている必要があります。 trustyai モデルのモニタリングを行います。OpenShift AI 2.7以降は削除されました。 ray 分散ワークロードに利用されます。 kueue 分散ワークロードに利用されます。 workbenches ワークベンチの作成に利用されます。 dashboard ダッシュボードを使用可能にするために利用されます。 modelmeshserving 中小規模モデルのサービングに利用されます。 datasciencepipelines MLOpsのパイプラインを作成するために利用されます。 trainingoperator 分散ワークロードに利用されます。 今回はYAMLビューを選択し、以下のように設定しました。 spec: components: codeflare: managementState: Managed kserve: serving: ingressGateway: certificate: type: OpenshiftDefaultIngress managementState: Managed name: knative-serving managementState: Removed trustyai: managementState: Removed ray: managementState: Managed kueue: managementState: Managed workbenches: managementState: Managed dashboard: managementState: Managed modelmeshserving: managementState: Managed datasciencepipelines: managementState: Managed trainingoperator: managementState: Removed DataScienceClusterの設定 Red Hat OpenShift AI の詳細画面のData Science Clusterタブに移動して、作成したインスタンスのステータスが「Phase:Ready」になっていれば作成完了です。 DataScienceCluster作成確認 正常性確認 OpenShift AIの正常性確認としてWebログイン、プロジェクトを作成、ワークベンチ作成、JupyterNotebookを起動するまでを行います。 webログイン OpenShiftコンソール画面の右上の格子状のアイコンを選択し、Red Hat OpenShift AIを開きます。 OpenShift AIの開き方 ログインを求められるのでKubeadmin or IDP設定したuserでログインします。 Data Science Projects作成 OpenShift AIコンソール画面の左のData Science Projectsを開きます。 右上のCreate data science projectを押します。 プロジェクト作成 プロジェクトの名前を記入してCreateを押せばプロジェクトを作成できます。 プロジェクトの名前設定 プロジェクトの詳細画面 ワークベンチ作成 作成したプロジェクトの画面から、Workbenchesタブに移動します。 Create workbenchを選択します。 ワークベンチの作成 以下の項目を設定して、Create Workbenchを選択します。 Name ワークベンチ名 Image selection ノートブックで扱いたいライブラリを選択 3 Container size ワークベンチのサイズ Accelerator GPUを用いる場合:NVIDIA GPU Cluster storage 新しく作るか既存のものを選択 ワークベンチの設定➀ ワークベンチの設定② 作成したワークベンチのStatusがRunning になって、Openのリンクが押せるようになったらワークベンチ作成完了です。 ワークベンチの作成を確認 JupyterNotebook起動 作成したワークベンチのOpenのリンクを開きます。 ログインを求められるのでログインします。 Authorize Accessという画面が出てくるので、Allow selected permissionsを押します。 JupyterNotebookにログインするときの認証 JupyterLabの画面が表示されればJupyterNotebookの起動が完了です。 JupyterLabの画面 最後に 以上の手順によりJupyterNotebookを起動することができました。これによりOpenShift上で機械学習を行うことができます。しかしながら、ここまではMLOpsの前半にすぎません。次回からはMLOpsで重要なパイプラインの設定やサービングの設定について解説する予定なので楽しみにしてください。 参考 OpenShift AIの要件: https://docs.redhat.com/ja/documentation/red_hat_openshift_ai_self-managed/2.13/html/installing_and_uninstalling_openshift_ai_self-managed/installing-and-deploying-openshift-ai_install#installing-and-deploying-openshift-ai_install コンポーネント設定: https://docs.redhat.com/ja/documentation/red_hat_openshift_ai_self-managed/2.13/html/installing_and_uninstalling_openshift_ai_self-managed/installing-and-managing-openshift-ai-components_component-install ノートブックのライブラリ: https://docs.redhat.com/ja/documentation/red_hat_openshift_ai_self-managed/2.13/html/working_on_data_science_projects/using-project-workbenches_projects#creating-a-workbench-select-ide_projects OpenShiftのGPU設定の方法について: https://tech-lab.sios.jp/archives/44040 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post OpenShift AI を導入してみた first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの佐藤 陽です。 今回はAzureのネットワーク周りの話です。 Azure Bastionを利用して、プライベートIPを介したセキュアなVM接続を実現する方法をご紹介します。 はじめに この記事に至った背景ですが、こちらをご参照ください。 【Azure】閉域化されたリソースに対して自分のPCからアクセスする方法を紹介 こちらの記事で、「閉域化されたリソースに対して自PCからVMを経由してアクセスする方法」をご紹介しました。 しかし、経由するためのVMがPublicIPを持っているためどこからでもアクセスできてしまい、セキュリティ面での不安が残ります。 そこで今回はこの不安を解消するため、プライベートIPを経由して接続できる Azure Bastion のご紹介をしたいと思います。 Azure Bastionとは Azure Bastionとは、Azureが提供するフルマネージドなPaaSであり、プライベートIPを介してVMに接続することを可能とするためのサービスです。 また後述しますが、このAzure Bastionに対する認証/認可としてはRBACの仕組みを利用することが可能です。 そのため結果として、RBACで設定されたユーザーのみが、Bastionを経由してVMにアクセスることが出来るようになります。 価格 Bastionを利用する際の価格に関しては、SKUや利用料に依存します。 以前はBasic以上のSKUしか存在しなく、最低金額でも月額2万円近い金額を支払う必要がありました。 ただ、前回の記事の中で取り上げた KeyVaultのシークレット情報書き換えたい CosmosDBの中身をデータエクスプローラーで見たい といったライトなユースケースだけであれば、それに対して月2万払うのは少しためらってしまいます。 しかし、最近 Developer SKU という新たな価格帯が登場しました!! 基本料金はなんと無料! 上記したような、ちょっとしたユースケースであれば無料で使うことができます。 まさに今回のようなユースケースに適したSKUです。 Basic以上のプラン比べて色々機能の制限はありますが、非常にライトに使えるようになっているため使い勝手は良いです。 今回影響する制限事項に関しては、以下のような点があります。 リージョン 接続できるVMの種別 Bastionへの接続方法 影響範囲に関しては、この後の記事の中でも言及していきます。 使ってみる では実際に、Bastionを使ってプライベートIPを介したVMの接続を試み、そこから閉域化されたKeyVaultにアクセスしてみたいと思います。 リソース作成 まずはリソースの事前準備です。 KeyVault 今回も検証用にKeyVaultを使っていきます。 前回の記事と同様になるため省略しますが、Azure KeyVaultを作成し RBACの設定やサンプルのシークレットを追加しておいてもらえるとよいかと思います。 Bastion&VNet 次に、早速Bastionを作っていきます。 Azure PortalからBastionのリソースを選択し、作成していきます。 ここでDeveloper SKUの制限1つ目、リージョンです。 Developer SKUでは現在以下のリージョンのみに対応しており、日本のリージョンに対応していません。 米国中部 EUAP 米国東部 2 EUAP 米国中西部 米国中北部 米国西部 北ヨーロッパ そのため今回は米国西部(WestUS)にて作成することにします。 そして、インスタンスのレベルとしては、 Developer を選択します。 次に、仮想ネットワークです。 これはAzure Bastionが接続する先の仮想ネットワークであり、VMが含まれるネットワークを指します。 前回の記事で作ったものを使い回すそうかと思ったのですが 今回BastionではWestUSのリージョンにデプロイしており、仮想ネットワークも同リージョンで作成する必要があるため、このタイミングでVNetを作り直します。 これでBastionの作成はOKです。 VM ではVMも作成していきます。 前回の記事とほぼ同じ流れですが、気を付ける点が3点あります。 上記で作成したWestUSのVNetに対してデプロイすること PublicIPを なし にすること RDPポート(3389)を開けておくこと なお、制限事項の2つ目としてBastionの接続先のVMに対する制限があり、以下の通りとなっています。 接続方法 判定 RDPを利用したWindowsVMへの接続 〇 SSHを利用したLinuxVMへの接続 〇 RDPを利用したLinuxVMへの接続 × SSHを利用したWindowsVMへの接続 × 今回はRDPを利用してWindowsVMへの接続を行おうとしていたので、影響はありません。 また下2つのケースは、そもそもそんなに発生しないなとも思いますが、利用するBasic以上のプランをご検討ください。 ということで、今回はこのまま作成していきます。 作成完了後、RDP接続しようとしても接続できないことが確認できます。 (PublicIPが存在していないため) これで必要なリソースは作成完了です。 これまでの流れをやっていただくと、以下7リソースが出来上がっているかと思います。 Bastion VNet VM Network Interface Network Security Group Disc KeyVault 権限付与 では、BastionからVMにアクセスするための権限割り当てを行います。 権限として必要なものは以下2つです。 仮想マシンに対する 閲覧者 ロール。 仮想マシンのプライベート IP を使用する NIC に対する 閲覧者 ロール。 対象のリソースとしては以下のものです。 なお、ここで割り当てるのはBastionのManagedIDではなくて、アクセスするユーザー(EntraID)のServicePrincipalとなります。 つまり、あくまでBastionは接続するためのツールであって、アクセスするのはAzureテナントにログインしているユーザーであることが、この点から分かります。 サービスエンドポイント設定 既にやっているよ、っていう方は読み飛ばしてもらってOKです。 先程VNetなどを作り直したので、KeyVaultに対するサービスエンドポイントの設定なども忘れず行っておきましょう。 接続 ここまでできれば準備ばっちりです。 あとはBastionを使って接続していきますが、ここで制限事項の3つ目です。 Basic以上のプランだとAzure CLIからなども接続できるようなのですが、DeveloperSKUだとAzure Portal上からの接続のみとなります。 とはいいつつ、今回の要件程度であれば大きな問題ではありません。 Azure PortalからVMを選択し、Bastionのブレードを選択します。 すると、デプロイしているBastionが選択されて居ることが確認できます。 ここでVMに対する認証情報を追加し、「接続」のボタンを押します。 そうすると、ブラウザ上でいつも通りのVMへの接続が始まり、操作が可能となることが分かります。 そして、前回同様VM内のブラウザからAzurePortalを開き、KeyVaultに接続を試みます。 格納しているシークレット情報が確認できました。 (もちろん自分のPCから直接閲覧した場合は見えないかと思います。) これで、 プライベートIPを介してVMに接続し、そこからサービスエンドポイント経由で閉域化されたリソースに対してアクセスできる ことが確認できました! まとめ 今回は、Azure Bastionを利用して、プライベートIPを介したVM接続を試してみました。 前回の記事ではパブリックIPを持った状態であったため、セキュリティに不安が残りましたが、今回のようなケースであればセキュリティも強固になったといえるかと思います。 また、DeveloperSKUを採用することでコストも抑えられますし、アクセス管理に関してもRBACで行えるため非常にスマートですね。 是非閉域化されたリソースに対して管理・運用する場合はBastionの採用を検討してみてください。 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【Azure】Bastionを利用してプライベートIPを介したセキュアなVM接続を実現する【基本無料】 first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの佐藤 陽です。 今回はAzureのネットワーク周りの話です。 閉域なネットワーク環境にあるKeyVaultやCosmosDBに対して、自分のPCからアクセスする方法をご紹介します。 はじめに Azureでシステムを構築するにあたって、閉域網として実現することはめずらしくないかと思います。 なお閉域網とは、外部のインターネットと接続せずに、特定の範囲内でのみアクセス可能なネットワークのことを指します。 KeyVaultや、CosmosDBなどのリソースをこの閉域網の中に閉じ込めることによって セキュリティやプライバシーを強化し、データの漏洩リスクを低減することができます。 この時、アプリを構築するうえでAzure上のAppServiceやAzure Functionsなどのコンピューティングリソースからはアクセスする必要がありますが そういった手段はもちろん用意されています。 (VNet統合) ただ、エンジニアが保守運用の際にアクセスしたいケースも往々にあります。 KeyVaultに格納されているシークレット情報を更新したい CosmosDBに蓄積されたデータを確認したい こういった際に、閉域とされているリソースに自分のPCからアクセスする方法を今回ご紹介します。 なお、Azure上で閉域網を実現するための方法として、様々な方法がありますが今回はサービスエンドポイントを使った方法をご紹介します。 事前準備 まずはKeyVaultを用意します。 KeyVaultのリソースを作成し、検証用にシークレットもひとつ追加しておきます。 まだこの時は閉域としていないため、自由にアクセスできるかと思います。 (アクセスポリシーや、RBACの設定は必要なのでそのあたりは適宜行ってください。) このKeyVaultを閉域化していきます。 KeyVaultのリソースの ネットワーク のブレードから設定を行います。 今回はサービスエンドポイントを利用するため 「ファイアーウォールと仮想ネットワーク」のタブから「特定の仮想ネットワークとIPアドレスからのパブリックアクセスを許可する」を選択します。 そうすると、仮想ネットワークの選択画面が出てくるかと思います。 まだ仮想ネットワークは作成していないため、設定を行わずにこのまま「適用」ボタンを押して終了します。 そして、この状態で再度シークレットを閲覧しに行ってみます。 すると ファイアウォールがオンになっており、クライアントの IP アドレスはこのキー コンテナーにアクセスする権限がありません と表示され、シークレットの中身が見えない事が確認できます。 もちろん作成なども行えません。 これでKeyVualtを閉域化することに成功しました!! 閉域化したリソースへのアクセス これで話は最初に戻りますが、この状態ではシークレットの追加や更新などが行えません。 「編集する時だけ閉域化を解除する…?」 と考える人もいるかと思いますが それがアンチパターンであることは、何となく察しがつきます。 それではどうするのかというと、Azure上に用意したVMを経由して閉域化されたリソースにアクセスします。 先程のKeyVaultのネットワーク設定の項目にもありましたが、アクセス元の仮想ネットワーク(VNet)を指定することができます。 以下の手順で接続を試みます。 VNet,Subnetを作成します。 Subnetに対してサービスエンドポイントの設定を有効にします。 Subnetに対してVMをデプロイします。 KeyVualtに対するアクセスを許可します。 RDP接続したVMを経由してKeyVaultへアクセスします。 VNet, Subnetの作成 まずはVNet, Subnetを作成します。 Subnet作成時にサービスエンドポイントの設定を行い、 Microsoft.KeyVault のサービスを追加します。 その他の項目はデフォルトのままでOKです。 SubnetへのVMデプロイ 今作成したサブネットの中にVMをデプロイします。 以下の内容でデプロイしました。 ポイントとしては以下2つです。 イメージをWindowsにする もちろんLinuxでも構いませんが、分かりやすさ優先で今回はWindowsとしています。 先程作成したサブネットに対してデプロイする KeyVaultに対するアクセスを許可する 先程作成したKeyVaultの ネットワーク ブレードから、上記で作成したサブネットからのアクセスを有効にします。 仮想ネットワークを追加したら[適用]を押して閉じます。 RDP接続したVMを経由してアクセス これで準備が整ったので、実際に自分のPCからRDPを利用してVMにアクセスします。 RDPでアクセスしたVM上でブラウザを開き、AzurePortalからのKeyVaultにアクセスします。 すると、以下のように先程格納したシークレットが閲覧できていることが確認できます。 振り返り 上記したステップを踏むことで閉域化のリソースにアクセスできることが確認できました。 今回はKeyVaultを題材にしましたが、CosmosDBや他のリソースに関しても同様の方法を取ることでアクセスが可能となります。 また、今回はサービスエンドポイントを利用しましたが、プライベートエンドポイントを利用した場合も同様の方法を取ることができます。 ただ、この時気になるのが「VM自体は外部のインターネットからアクセスできてるけどいいの?」といったところかと思います。 確かにKeyVaultに対しては外部のインターネットからのアクセスは遮断されていますが、 そこにアクセスするためのVMが、外部のインターネットからアクセスできてしまっている点について、不安が残ります。 この問題を解消する方法として、Azureには Azure Bastion というサービスが用意されています。 このBastionを使うことによって、VMのプライベートIPを介して自分のPCからセキュアに接続することが可能となります。 Bastionを利用した方法は以下の記事にまとめましたのでこちらもご覧ください。 【Azure】Bastionを利用してプライベートIPを介したセキュアなVM接続を実現する【基本無料】 まとめ 今回は、閉域化されたリソースに対して自分のPCからアクセスする方法をご紹介しました。 これで保守・運用についても自分のPCから問題なく行えなくなりそうです。 一方で、VMに対して外部インターネットからのアクセスが許可されているという課題もあります。 これを解消するBastionについても別記事にてご紹介しているため、こちらも併せてご覧ください。 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【Azure】閉域化されたリソースに対して自分のPCからアクセスする方法を紹介 first appeared on SIOS Tech. Lab .
はじめに 皆さんこんにちは!エンジニアの細川です。 皆さんは環境変数を扱うときに型付けをどのように行っていますか? いろいろ方法はあると思いますが、今回はNestJSでt3envというパッケージを利用する方法について紹介したいと思います! t3envはESMOnlyのパッケージになりますので、NestJSでESMOnlyパッケージを利用したい方の参考にもなれば幸いです! 注意点 今回紹介する方法はNode.JSのver.が22以上でないと利用できません。 前提 今回の各パッケージなどのバージョンは以下になります。 パッケージ バージョン Node.js v22.9.0 NestJS v10.0.0 t3env 0.11.1 t3envとは? t3env は環境変数を型安全に利用できるパッケージです。 皆さん、ご存知の通り通常環境変数は process.env などで取得すると思います。型が string | undefined であり、使いにくかったり、設定し忘れていることに気づけなかったりなど皆さんも環境変数に悩まされることは多いのではないでしょうか? t3envを利用するとzodで環境変数にvalidationをつけることができます。 また、以下のようにサジェストをしてくれたり説明を付けられたりと、ちょっとした定義をするだけで、かなり環境変数を使いやすくなります! また、NestJSで利用する場合、環境変数が設定されていないと、以下のように環境変数がセットされていないエラーを出してくれてサーバーを起動できないため、設定し忘れを防ぐこともできます。 t3envの導入 以下のコマンドを叩いて、t3envを導入します。NextやNuxtで使う場合はそれぞれ用のパッケージを導入すれば良さそうですが、今回はNestで利用するので、coreのパッケージを導入します。 npm install @t3-oss/env-nextjs zod pnpmの場合は以下コマンドを叩いてください。 pnpm add @t3-oss/env-nextjs zod NextやNuxtの場合はそれぞれの導入の仕方を 公式ドキュメント に記載してくれているので、そちらを参考にしてください。 定義ファイルの作成 t3envでは環境変数の定義用のファイルを作成する必要があるので、公式ドキュメントに従って、以下のように src/env.ts を作成します。 import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; export const env = createEnv({ server: { /** * DB接続情報 <- このように環境変数の説明も書けます。 */ DATABASE_URL: z.string().url(), OPEN_AI_API_KEY: z.string().min(1), }, /** * The prefix that client-side variables must have. This is enforced both at * a type-level and at runtime. */ clientPrefix: "PUBLIC_", client: { PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), }, /** * What object holds the environment variables at runtime. This is usually * `process.env` or `import.meta.env`. */ runtimeEnv: process.env, /** * By default, this library will feed the environment variables directly to * the Zod validator. * * This means that if you have an empty string for a value that is supposed * to be a number (e.g. `PORT=` in a ".env" file), Zod will incorrectly flag * it as a type mismatch violation. Additionally, if you have an empty string * for a value that is supposed to be a string with a default value (e.g. * `DOMAIN=` in an ".env" file), the default value will never be applied. * * In order to solve these issues, we recommend that all new projects * explicitly specify this option as true. */ emptyStringAsUndefined: true, }); このようにserverとclientで利用する環境変数を分けて定義できたり、zodのschemaによるvalidationも行うことができます。 より細かい定義方法は公式ドキュメントなどを参照してみてください。 こちら の記事でも分かりやすく書いてくれていました。 NestJSでESMOnlyパッケージを利用する方法 通常ではこれだけで利用することができるのですが、実はt3envはESMOnlyのパッケージであり、commonJSを採用しているNestJSではそのままでは利用することができません。NestJSをESM化する方法などもありますが、既存のコードがある状態だと変更に不安があるうえに、変更量も多くなってしまい、大変です。そこで、今回はNode.jsの --experimental-require-module オプションを利用します。こちらのオプションはNode.jsのver.22でリリースされたものののようですので、22以下の方は利用できないかもしれません( 参考 )。 package.jsonのNestの起動コマンドに --experimental-require-module オプションを追加します。 // package.json "build": "NODE_OPTIONS='--experimental-require-module' nest build", "start": "NODE_OPTIONS='--experimental-require-module' nest start", "start:dev": "NODE_OPTIONS='--experimental-require-module' nest start --watch", "start:debug": "NODE_OPTIONS='--experimental-require-module' NODE_ENV=development DEBUG=true nest start --debug --watch", "start:prod": "NODE_OPTIONS='--experimental-require-module' node dist/main", ...その他のコマンド これを追記するとNestJSのサーバーが立ち上げられるようになります。 他のESMOnlyのパッケージを導入する際もこの手順で使えるようになるはずです。 moduleResolutionエラーの対処 サーバー自体は先ほどのオプションを追加すれば立ち上がるようになるのですが、 src/env.ts のファイルを見ると以下のようなエラーが出ていると思います。 エラーをそのまま読むと、 moduleResolution という項目を node16 もしくは、 nodenext 、もしくは bundler にすればいいとのことですが、この設定にするためにはtsconfig.jsonの module も commonjs ではなく nodenext などにする必要があります。 moduleResolution はパス解決のための設定のようなので( 参考 )、 moduleResolution を設定するのではなく、t3envのパスを直接解決してあげることでこの問題に対処します。 具体的には、tsconfig.jsonに以下の記述を追記します。 { "compilerOptions": { "module": "commonjs", // 他の記述 "paths": { "@t3-oss/env-core": [ "../../node_modules/@t3-oss/env-core/dist/index.d.ts" // t3envのパッケージの実体へのパス ] } }, "include": [ "src/**/*.ts", "../../node_modules/@t3-oss/env-core/dist/index.d.ts" // <- こちらも追記 ] } 念のため全文も掲載しておきます。 もし追記して動作しないようでしたら、他の設定項目も確認してみてください。 { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": true, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false, "paths": { "@t3-oss/env-core": [ "../../node_modules/@t3-oss/env-core/dist/index.d.ts" ] } }, "include": [ "src/**/*.ts", "../../node_modules/@t3-oss/env-core/dist/index.d.ts" ] } 利用方法 以上で準備完了です。 利用する際は、まず通常と同じ通り、 .env などに実際の値を記載しておきます。 そして以下のように src/env.ts からimportを記述することで利用することができます。 import { env } from "./env"; const dbUrl = env.DATABASE_URL 型やサジェストも正しく機能していることが確認できるかと思います。 テストについて 今回の方法を試したところ、僕の環境ではJestでtestが動きませんでした。ちょうどvitestに切り替えるタイミングでしたので、Jestで動くようにする対応は行いませんでした。 vitestでは特に設定しなくてもESMも扱えるので、vitestでは問題なく動作しました。もしJestで動作しない方はvitestの導入も検討してみてください。NestJSでVitestを導入する際は こちら の記事を参考にさせていただきました。 まとめ t3envを利用することで、環境変数をサジェストしてくれたり、型安全に利用できたり、定義し忘れを防止することができる! NestJSでESMOnlyパッケージを利用する際には、 --experimental-require-module オプションを利用する "build": "NODE_OPTIONS='--experimental-require-module' nest build", "start": "NODE_OPTIONS='--experimental-require-module' nest start", "start:dev": "NODE_OPTIONS='--experimental-require-module' nest start --watch", "start:debug": "NODE_OPTIONS='--experimental-require-module' NODE_ENV=development DEBUG=true nest start --debug --watch", "start:prod": "NODE_OPTIONS='--experimental-require-module' node dist/main", t3envの型定義のパス解決のために、tsconfig.jsonに以下を追記する。 { "compilerOptions": { "module": "commonjs", // 他の記述 "paths": { "@t3-oss/env-core": [ "../../node_modules/@t3-oss/env-core/dist/index.d.ts" // t3envのパッケージの実体へのパス ] } }, "include": [ "src/**/*.ts", "../../node_modules/@t3-oss/env-core/dist/index.d.ts" // <- こちらも追記 ] testはvitestだと楽! おわりに 今回はNestJSで環境変数を型安全に利用する手段としてt3envを採用しました。 --experimental-require-module オプションのおかげで、NestJSでもESMOnlyのパッケージでも導入しやすくなったと思うので、ぜひ皆さんもt3envや他のESMパッケージの利用を検討してみてください。 他にもTypeScriptのあれこれを 記事 にしているので、良かったら読んでみてください! 参考にさせていただいた記事 https://zenn.dev/ptna/articles/28b20f303a3cfb https://zenn.dev/hayato94087/articles/3e4128feddffb9 https://env.t3.gg/docs/core   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post NestJSで環境変数を型安全に使おう! t3env first appeared on SIOS Tech. Lab .
以前の記事 では、CI 部分を担う OpenShift Pipelines について解説しました。続いて、本記事では実際に OpenShift Pipelines の CI 部分 を構築したいと思います。 構築の概要 Red Hat のチュートリアル を参考にして図のような CI フローを OpenShift 上に構築します。チュートリアルではパブリックリポジトリを利用していますが、実際構築する際はプライベートリポジトリであるケースが多いため、プライベートリポジトリで構築してみます。CI フローの流れとしては、まず初めにプライベートな Git リポジトリに変更をプッシュした際に EventListener への Webhook がトリガーされます。EventListener は受け取った情報から指定されたパラメータを Trigger Template にバインディングして PipelineRun を実行させます。PipelineRun では最初にリポジトリからソースコードの取得をします。次にソースコードのimage をビルドし、image registry にプッシュしたらフロー終了です。 事前準備 下記構成のプライベートなアプリケーション用Git リポジトリを用意します。アプリケーションの設定や定義をDockerfileとindex.phpで設定し、Kubernetesのリソースはk8sディレクトリ配下にまとめています。 . ├── Dockerfile ├── index.php └── k8s  ├── deployment.yaml  ├── route.yaml  └── service.yaml Dockerfile FROM image-registry.openshift-image-registry.svc:5000/openshift/php:latest ADD ./index.php /var/www/html/ CMD ["php","-S","0.0.0.0:8080","-t","/var/www/html"] index.php Hello world! k8s/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata:   labels:     app: ocp-cicd-appli-ui   name: ocp-cicd-appli-ui spec:   replicas: 1   selector:     matchLabels:       app: ocp-cicd-appli-ui   template:     metadata:       labels:         app: ocp-cicd-appli-ui     spec:       containers:         - image: image-registry.openshift-image-registry.svc:5000/pipelines-tutorial/ocp-cicd-appli-ui           imagePullPolicy: Always           name: ocp-cicd-appli-ui           ports:             - containerPort: 8080               protocol: TCP k8s/route.yaml apiVersion: route.openshift.io/v1 kind: Route metadata:   labels:     app: ocp-cicd-appli-ui   name: ocp-cicd-appli-ui spec:   port:     targetPort: 8080-tcp   to:     kind: Service     name: ocp-cicd-appli-ui     weight: 100 k8s/service.yaml apiVersion: v1 kind: Service metadata:   labels:     app: ocp-cicd-appli-ui   name: ocp-cicd-appli-ui spec:   type: NodePort   ports:     - name: 8080-tcp       port: 8080       targetPort: 8080       protocol: TCP   selector:     app: ocp-cicd-appli-ui 前提条件 OpenShift クラスターが構築済みであること oc CLI がインストール済みであること tkn CLI がインストール済みであること プライベートな Git リポジトリにssh設定がされており、対となる秘密鍵を持っていること リポジトリ名に”_”が含まれていないこと( “_”が含まれているとPipelineRunリソースが正常に作成できない点に注意 ) OpenShift Pipelines をインストール OpenShift OperatorHub で Administrator のパースペクティブにいることを確認します。 Web コンソールで[Operators] > [OperatorHub]に移動します。 検索バーに「OpenShift Pipelines」と入力して、 OpenShift Pipelines をクリックして Install をクリックします。 デフォルトのまま Install をクリックします。 パイプラインを作成 図の赤枠部分を作成していきます。 サンプルアプリケーションをデプロイ 新規プロジェクトを作成します。 $ oc new-project pipelines-tutorial Now using project "pipelines-tutorial" on server "URL". You can add applications to this project with the 'new-app' command. For example, try: oc new-app rails-postgresql-example to build a new example application in Ruby. Or use kubectl to deploy a simple Kubernetes application: kubectl create deployment hello-node --image=registry.k8s.io/e2e-test-images/agnhost:2.43 -- /agnhost serve-hostname ssh接続情報の作成 プライベートな Git リポジトリに接続するためのシークレットを作成します。 $ oc apply -f secret.yaml secret/ssh-credential created secret.yaml apiVersion: v1 kind: Secret metadata:   name: ssh-credential data:   id_rsa: # cat ~/.ssh/id_rsa | base64 で表示された内容を記入(一行で記述すること) pipeline サービスアカウントを確認 パイプラインを実行するサービスアカウントが存在しているか確認します。 $ oc get serviceaccount pipeline NAME SECRETS AGE pipeline 1 102s タスクをインストール(実行確認のためのCD部分のタスク) タスクを作成します。これは実行確認のための CD 部分のタスクとなり、続編のCD部分を実装する際は実行しないでください。 $ oc create -f apply_manifest_task.yaml task.tekton.dev/apply-manifests created $ oc create -f update_deployment_task.yaml task.tekton.dev/update-deployment created apply_manifest_task.yaml apiVersion: tekton.dev/v1 kind: Task metadata:   name: apply-manifests spec:   workspaces:   - name: source   params:     - name: manifest_dir       description: The directory in source that contains yaml manifests       type: string       default: "k8s"   steps:     - name: apply       image: image-registry.openshift-image-registry.svc:5000/openshift/cli:latest       workingDir: /workspace/source       command: ["/bin/bash", "-c"]       args:         - |-           echo Applying manifests in $(inputs.params.manifest_dir) directory           oc apply -f $(inputs.params.manifest_dir)           echo ----------------------------------- update_deployment_task.yaml apiVersion: tekton.dev/v1 kind: Task metadata:   name: update-deployment spec:   params:     - name: deployment       description: The name of the deployment patch the image       type: string     - name: IMAGE       description: Location of image to be patched with       type: string   steps:     - name: patch       image: image-registry.openshift-image-registry.svc:5000/openshift/cli:latest       command: ["/bin/bash", "-c"]       args:         - |-           oc patch deployment $(inputs.params.deployment) --patch='{"spec":{"template":{"spec":{             "containers":[{               "name": "$(inputs.params.deployment)",               "image":"$(inputs.params.IMAGE)"             }]           }}}}'           patched_at_timestamp=`date +%s`           oc patch deployment $(inputs.params.deployment) --patch='{"spec":{"template":{"metadata":{             "labels":{               "patched_at": '\"$patched_at_timestamp\"'             }           }}}}' 作成したタスクを確認 タスクが作成されていることを確認します。 $ tkn task ls NAME DESCRIPTION AGE apply-manifests 6 minutes ago update-deployment 6 minutes ago パイプラインを作成 パイプラインを作成します。 $ oc create -f pipeline.yaml pipeline.tekton.dev/build-and-deploy created pipeline.yaml apiVersion: tekton.dev/v1 kind: Pipeline metadata:   name: build-and-deploy spec:   workspaces:   - name: shared-workspace   - name: ssh-creds   params:   - name: deployment-name     type: string     description: name of the deployment to be patched   - name: git-url     type: string     description: url of the git repo for the code of deployment   - name: git-revision     type: string     description: revision to be used from repo of the code for deployment     default: main   - name: IMAGE     type: string     description: image to be build from the code     tasks:   - name: fetch-repository     taskRef:       name: git-clone       kind: ClusterTask     workspaces:     - name: output       workspace: shared-workspace     - name: ssh-directory       workspace: ssh-creds     params:     - name: url       value: $(params.git-url)     - name: subdirectory       value: ""     - name: deleteExisting       value: "true"     - name: revision       value: $(params.git-revision)   - name: build-image     taskRef:       name: buildah       kind: ClusterTask     params:     - name: IMAGE       value: $(params.IMAGE)     workspaces:     - name: source       workspace: shared-workspace     runAfter:     - fetch-repository # 以下CD部分のタスクのため、続編のCD部分を実装する際は記入しないこと   - name: apply-manifests     taskRef:       name: apply-manifests     workspaces:     - name: source       workspace: shared-workspace     runAfter:     - build-image   - name: update-deployment     taskRef:       name: update-deployment     params:     - name: deployment       value: $(params.deployment-name)     - name: IMAGE       value: $(params.IMAGE)     runAfter:     - apply-manifests 作成したパイプラインを確認 パイプラインが作成されていることを確認します。 $ tkn pipeline ls NAME AGE LAST RUN STARTED DURATION STATUS build-and-deploy 2 minutes ago --- --- --- --- パイプラインを手動実行 作成したパイプラインを手動で実行して動作を確認します。 $ tkn pipeline start build-and-deploy \ --prefix-name build-deploy-ui-pipelinerun \ -w name=shared-workspace,volumeClaimTemplateFile=persistent_volume_claim.yaml \ -w name=ssh-creds,secret=ssh-credential \ -p deployment-name=ocp-cicd-appli-ui \ -p git-url=<リポジトリ名:git@xxx:xxx/xxx.gitの形式> \ -p IMAGE=image-registry.openshift-image-registry.svc:5000/pipelines-tutorial/ocp-cicd-appli-ui \ --use-param-defaults PipelineRun started: build-deploy-ui-pipelinerun-ngnjn In order to track the PipelineRun progress run: tkn pipelinerun logs build-deploy-ui-pipelinerun-ngnjn -f -n pipelines-tutorial persistent_volume_claim.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata:   name: source-pvc spec:   accessModes:     - ReadWriteOnce   resources:     requests:       storage: 500Mi Pipelinesを確認するとPipelineが実行中になっています。 Pipeline run を確認するとパイプラインの実行ログなどを確認することが出来ます。Succeeded になっていることを確認します。 アプリケーションのルートを取得 デプロイしたアプリケーションのルートを取得して正しくデプロイされていることを確認します。 $ oc get route ocp-cicd-appli-ui --template='http://{{.spec.host}}' <アプリケーションのURL> トリガーを作成 図の赤枠部分を作成していきます。 テンプレートの適用 トリガーテンプレートを作成します。 $ oc create -f triggertemplate.yaml triggertemplate.triggers.tekton.dev/sample-app created triggertemplate.yaml apiVersion: triggers.tekton.dev/v1beta1 kind: TriggerTemplate metadata:   name: sample-app spec:   params:   - name: git-repo-url     description: The git repository url   - name: git-revision     description: The git revision     default: main   - name: git-repo-name     description: The name of the deployment to be created / patched   resourcetemplates:   - apiVersion: tekton.dev/v1     kind: PipelineRun     metadata:       generateName: build-deploy-$(tt.params.git-repo-name)     spec:       taskRunTemplate:         serviceAccountName: pipeline       pipelineRef:         name: build-and-deploy       params:       - name: deployment-name         value: $(tt.params.git-repo-name)       - name: git-url         value: $(tt.params.git-repo-url)       - name: git-revision         value: $(tt.params.git-revision)       - name: IMAGE value: image-registry.openshift-image-registry.svc:5000/$(context.pipelineRun.namespace)/$(tt.params.git-repo-name):$(tt.params.git-revision)       # Pipelineで実行結果確認のCD部分を実装する場合はコミットIDを参照しないため下記に置き換える         # value: image-registry.openshift-image-registry.svc:5000/$(context.pipelineRun.namespace)/$(tt.params.git-repo-name)       workspaces:       - name: shared-workspace         volumeClaimTemplate:           spec:             accessModes:               - ReadWriteOnce             resources:               requests:                 storage: 500Mi       - name: ssh-creds         secret:           secretName: ssh-credential バインディングの適用 トリガーバインディングを作成します。 $ oc create -f triggerbinding.yaml triggerbinding.triggers.tekton.dev/sample-app created triggerbinding.yaml apiVersion: triggers.tekton.dev/v1beta1 kind: TriggerBinding metadata:   name: sample-app spec:   params:   - name: git-repo-url     # privateリポジトリのためssh_urlを参照     value: $(body.repository.ssh_url)   - name: git-repo-name     value: $(body.repository.name)   - name: git-revision     value: $(body.head_commit.id) トリガーの適用 トリガーを適用します。webhookのsecretも作成するため、後程トークンの値を使います。 $ oc create -f trigger.yaml trigger.triggers.tekton.dev/sample-trigger created secret/github-secret created trigger.yaml apiVersion: triggers.tekton.dev/v1beta1 kind: Trigger metadata:   # event_listenerで参照するTrigger名   name: sample-trigger spec:   serviceAccountName: pipeline   interceptors:     - ref:         name: "github"       params:         - name: "secretRef"           value:             secretName: github-secret             secretKey: secretToken         - name: "eventTypes"           value: ["push"]   bindings:     - ref: sample-app   template:     ref: sample-app --- apiVersion: v1 kind: Secret metadata:   name: github-secret type: Opaque stringData: # webhookのシークレット   secretToken: "1234567" EventListenerの作成 EventListenerを作成します。 $ oc create -f event_listener.yaml eventlistener.triggers.tekton.dev/sample-app created event_listener.yaml apiVersion: triggers.tekton.dev/v1beta1 kind: EventListener metadata:   name: sample-app spec:   serviceAccountName: pipeline   triggers:     # webhookをインターセプトするTrigger名を参照     - triggerRef: sample-trigger EventListener サービスをルートとして公開 EventListener サービスをルートとして公開します。 $ oc expose svc el-sample-app route.route.openshift.io/el-sample-app exposed webhook-url を取得 webhook-url を取得を取得します。取得したURLはGit リポジトリのwebhook設定に使用します。 $ echo "URL: $(oc get route el-sample-app --template='http://{{.spec.host}}')" URL: <webhook-url> Webhookを手動設定する Git リポジトリにWebhookを設定します。 GitHubのリポジトリを開きます。 Settings > Hook > Add Webhook をクリックします。 payload URL に webhook-url を追加 > コンテンツタイプをapplication/jsonに選択 > シークレットを追加 例:1234567 > Add Webhook をクリックして設定完了です。 パイプライン実行テスト Git リポジトリの index.php に適当な変更を加え、コミットを Git リポジトリにプッシュします。 git add . git commit -m "hogehoge" git push OpenShift WebConsole Developer パースペクティブを見ると、PipelineRun が自動的に作成されます。 Succeeded になっていることを確認します。 続いて、アプリケーションに加えた変更が反映されていることを確認して完了です。 まとめ 本記事では実際に Red Hat のチュートリアルを参考にしてOpenShift Pipelines の CI 部分を構築してみました。実行確認のために CD 部分を実装しましたが、これはマニフェストファイルを適用しただけの push 型のデプロイでした。次回は CD 部分となる OpenShift GitOps を構築して デプロイ先の状態変化を検知してマニフェストファイルの状態を保ち続ける pull 型のデプロイを実装してみます。 参考文献 https://github.com/openshift/pipelines-tutorial/tree/master ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Openshift Pipelines を構築してみた first appeared on SIOS Tech. Lab .
概要 こんにちは、サイオステクノロジーの安藤 浩です。 Bicepは、Azureリソースを宣言的にデプロイするためのドメイン固有言語(DSL)であり、ARMテンプレートの記述を簡素化することを目的としています。ARM テンプレートは、JSON または Bicep の使用という 2 つの異なる方法で記述できますが、JSONの場合は複雑な記法が必要になります。 私自身ARM テンプレート は書いたことないですが、ARM テンプレート自体複雑な印象で記述が困難なように思います。 JSON と Bicep の比較は Bicep プレイグラウンド で確認することができます。 以下のAzure のLearning があるのでそちらを参照するとBicep に関して学習できます。 Bicep に関する Learn モジュール 前提条件 以下をインストールします。 ツール Version Visual Studio Code Version: 1.94.2 Visual Studio Code 用の Bicep 拡張機能 0.30.23 最新の Azure CLI ツールまたは最新の Azure PowerShell バージョン 2.65.0 構成要素 今は例として以下の構成要素でBicep によるAzure のインフラ構築をしてみます。 Azure Function Blob Storage Application Insights LogAnalytics Workspace App Service Plan ソースコード ソースコードは以下にあります。 https://github.com/Hiroshi-Ando-sti/introduction-bicep 実装方法 まずは、  main.bicep  を用意し、以下を記載します。 targetScope = 'resourceGroup' param location string = resourceGroup().location @description('The environment designator for the deployment. Replaces {env} in namingConvention.') @allowed([ 'dev' //Develop 'stg' //Staging 'prd' //Production ]) param enviromentName string = 'dev' var enviromentResourceNameWithoutHyphen = replace(enviromentName, '-', '') @allowed(['northeurope', 'southeastasia', 'eastasia', 'eastus2', 'southcentralus', 'australiaeast', 'eastus', 'westus2', 'uksouth', 'eastus2euap', 'westus3', 'swedencentral']) param hostingPlanLocation string = 'eastus2' @description('The workload name. Replaces {workloadName} in namingConvention.') param workloadName string = 'pj' param deploymentStorageContainerName string = 'app-pkg-func' @description('secretName') param secretName1 string @secure() @description('secretValue') param secretValue1 string var suffixResourceName = '-${workloadName}' var suffixResourceNameWithoutHyphen = replace(suffixResourceName, '-', '') var uniqueStr = uniqueString(resourceGroup().id, enviromentName, workloadName, suffixResourceNameWithoutHyphen) param convertedEpoch int = dateTimeToEpoch(dateTimeAdd(utcNow(), 'P1Y')) var abbrs = json(loadTextContent('abbreviations.json')) var tags = { workload: workloadName environment: enviromentName } 記述方法 主要なBicep の記述方法について説明します。 param(パラメータ) param は、実行時に値をBicepテンプレートに渡すためのパラメータを定義するために使用されます。これにより、テンプレートの再利用が可能になり、値のハードコーディングを避けられます。 例: @allowed([ 'dev' //Develop 'stg' //Staging 'prd' //Production ]) param enviromentName string = 'dev' ここでは、 enviromentName という名前の string 型のパラメータが定義されており、デフォルト値: dev が設定されています。 型 :  string 、 int 、 bool 、 array 、 object  などの型を指定できます。 デフォルト値 : パラメータにデフォルト値を設定することも可能です。 description description は、パラメータや変数の説明を提供するために使用されます。 allowed allowed は、パラメータの許可された値を制限するために使用されます。これにより、指定値以外の不正な値を設定することを防ぐことができます。 var(変数) var は、テンプレート内で中間的な値や計算結果を保持するための変数を定義するために使用されます。 例: var uniqueStr = uniqueString(resourceGroup().id, enviromentName, workloadName, suffixResourceNameWithoutHyphen) この例では、 uniqueStr  という変数に、リソースグループのID, enviromentName などに基づいて生成された一意の文字列が設定されています。 式 : 変数は、式や文字列の連結、計算などを保持できます。 スコープ : 変数はテンプレート内でのみ使用可能で、外部には公開されません。 secure(セキュアパラメータ) Bicepでは、パスワードやAPIキーのような機密情報を取り扱う場合、セキュアなパラメータを定義できます。ログや出力には表示されず、デプロイメント中に値が安全に処理されます。 例: @secure() @description('secretValue') param secretValue1 string module(モジュール) module  は、他のBicepテンプレートを参照して再利用するために使用されます。テンプレートを分割して、管理しやすくなります。例えば、Azure Function や AppService などのリソースに対するStorage Accountを作成する際に再利用可能なモジュールを作成できます。 例: ./main.bicep var storageAccountName = '${abbrs.storageStorageAccounts}${enviromentResourceNameWithoutHyphen}${uniqueStr}' module storage 'modules/storage/storage.bicep' = { name: 'storage' params: { storageAccountName: storageAccountName location: location tags: tags storageAccountType: 'Standard_LRS' containerNames: [deploymentStorageContainerName] } } ./modules/storage/storage.bicep @description('Storage Account type') @allowed([ 'Premium_LRS' 'Premium_ZRS' 'Standard_GRS' 'Standard_GZRS' 'Standard_LRS' 'Standard_RAGRS' 'Standard_RAGZRS' 'Standard_ZRS' ]) param storageAccountType string = 'Standard_LRS' @description('The storage account location.') param location string = resourceGroup().location param tags object = {} @description('The name of the storage account') param storageAccountName string param containerNames array resource sa 'Microsoft.Storage/storageAccounts@2023-01-01' = { name: storageAccountName location: location tags: tags sku: { name: storageAccountType } kind: 'StorageV2' properties: {} } resource blobServices 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = { parent: sa name: 'default' } resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = [for containerName in containerNames: { parent: blobServices name: containerName }] output storageAccountName string = storageAccountName output storageAccountId string = sa.id この例では、module を利用し、  ./modules/storage/storage.bicep  をインポートして再利用しています。 モジュール化 : 複雑なインフラを整理し、リソースをモジュールにグループ化するのに役立ちます。 再利用 : モジュールは、異なるデプロイメントで再利用可能です。 resource(リソース) resource  は、Azureリソースを定義するために使用されます。リソースの種類、プロパティ、依存関係など、リソースの詳細を定義します。 リソースの定義は、 Microsoft.Storage/storageAccounts@2023-01-01  という形式で指定します。リソースの種類とAPIバージョンを指定しています。 詳しくは、 リソースの Bicep を使った宣言  を参照ください。 Storage Accountの場合は、以下を参照して詳細なプロパティなどを指定します。 https://learn.microsoft.com/ja-jp/azure/templates/microsoft.storage/storageaccounts output(出力) output  は、デプロイメントが完了した後に値を返すために使用されます。リソースIDや接続文字列のような情報をデプロイ後に返す場合に便利です。 上記の例では、作成されたストレージアカウントのリソースIDを返します。 データの返却 : 出力された値は、後続のデプロイメントや追加のアクションで使用できます。 複数の出力 : 1つのBicepファイルに複数の  output  ステートメントを含めることができます。 その他 Best practice  の記載があるのでこちらを参考にしてBest Practice を実践しましょう。 パラメーター ファイルを使用して値を指定する Bicep では、パラメーター ファイルを使用して、Bicep テンプレートに渡す値を指定することができます。 パラメーター ファイルは、JSON 形式のファイルで、Bicep テンプレートのパラメータ(param)に渡す値を定義します。 main.parameters.dev.json  を  main.bicep  と同階層に作成します。 { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "location": { "value": "eastus2" }, "enviromentName": { "value": "dev" }, "hostingPlanLocation": { "value": "eastus2" }, "workloadName": { "value": "pj-bicep" }, "deploymentStorageContainerName": { "value": "app-pkg-func" }, "secretName1": { "value": "secretName1" }, "secretValue1": { "value": "secretValue1" } } } 新規作成 リソースグループ作成 リソースグループを作成します。 az group create --name rg-dev-pj-bicep --location japaneast デプロイ 上記で作成したリソースグループへリソースをデプロイします。 az deployment group create \ --name rg-dev-pj-bicep-deploy \ --resource-group rg-dev-pj-bicep \ --mode Complete \ --confirm-with-what-if \ --template-file main.bicep \ --parameters ./main.parameters.dev.json ここでは  mode  にComplete と指定して、完全モードでDeployしています。 完全モードは、 Bicep コードに 定義されているリソースのみをデプロイするモードです。Bicep コードに存在しない既存のリソースが存在する場合は、削除されます。 もう一方、インクリメントモードがあり、Bicep コードに存在しない既存のリソースが存在してもリソースの影響は受けません。 更新 リソースの作成が完了したら、もう一度同じコマンドを実行すると変更箇所の差分が確認できます。 az deployment group create \ --name rg-dev-pj-bicep-deploy \ --resource-group rg-dev-pj-bicep \ --mode Complete \ --confirm-with-what-if \ --template-file main.bicep \ --parameters ./main.parameters.dev.json 実行すると以下のように変更箇所を教えてくれます。 確認方法 コマンドの結果でも確認できますが、リソースグループの「概要」の「要点」の箇所のデプロイのところを確認することも可能です。 リンクを押下すると以下のようになります。 リンクの function, appInsights, storage などは  main.bicep  の  module  で指定した name に対応します。より詳細は各リンクを押下すると確認できます。 参考URL Bicep とは Bicep のドキュメント Bicep の基礎 Bicep に関するベスト プラクティス サンプルコード まとめ Bicep を利用して、簡単に Azure のインフラ構築をしてみました。割と簡単な記述でリソース作成ができるので、ぜひ使ってはいかがでしょうか。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Bicep によるAzure のインフラ構築入門 first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの佐藤 陽です。 先日Ragasの大きめなアップデート(0.2.x)があり、色々と変更点が見られました。 今回はそのアップデート内容をチェックしていきたいと思います。 Ragasって何? 今Ragas使ってるけど、どんな変更があったの? 公式ドキュメント読んでもよくわからないからサクっと教えて! といった方は最後までご覧ください! はじめに Ragasって何?という方は以下の記事をご覧ください! こちらの記事は前のバージョン(0.1.x)での紹介にはなりますが、概念的な部分は変わっていないので 一度読んでいただくことで、今回のアップデート内容も理解しやすいかと思います。 【初心者向け】RAG評価フレームワーク Ragasを必要最低限で使ってみる 今回このRagasにバージョンアップがあり、0.1.xから0.2.xになりました。 そのアップデート内容の確認と、サンプルコードを交えて挙動の確認をしていきたいと思います。 なお、手元の環境では ragas==0.2.3 のバージョンを利用して検証行っています。 何が変わったのか? 公式 にもまとめられていました。 変更点の概要としては以下の通りです。 評価用に与えるデータセットのクラス変更 メトリクスの追加および、利用方法の変更 テストセット生成のコスト効率改善 プロンプトオブジェクトの変更 もう少し細かく見ていきたいと思います。 データセットの型変更 Ragasには以下の4つの値を渡すことで評価を行うことができます。 そしてそれらの名前にも変更がありました。 新名称 何? 旧称 user_input ユーザーからの質問内容 question response RAGから返された回答内容 answer retrieved_contexts RAGが外部DBから取得し、参考にした内容 contexts reference user_inputに対する真の回答内容 ground_truth これらの値をデータセットとして評価関数に渡し、評価を行ってもらいます。 そして、0.2.xへのアップデートによってこのデータセットのクラスにも変更がありました。 以前はHuggingFaceというライブラリの Datasets というものを利用していましたが、0.2.xでは独自の EvaluationDataset というクラスを利用しています。 このデータセットの用意の仕方を3つご紹介します。 Datasetsからの変換 前バージョンで利用していたDatasetsからEvaluationDatasetの変換方法が提供されています。 以下のような形で簡単に変換することが可能です。 from datasets import load_dataset from ragas import EvaluationDataset dataset = load_dataset("explodinggradients/amnesty_qa","english_v3") eval_dataset = EvaluationDataset.from_hf_dataset(dataset["eval"]) CSVからの変換 EvaluationDataset->csvの変換の処理に関しても紹介されていました。 # load eva dataset eval_dataset = EvaluationDataset.from_csv("path/to/save/dataset.csv") Evaluation Sampleを利用した生成 ここが大きな変更点です。 恐らく0.2.x系からだと思うのですが、 Evaluation Sample という概念が出てきました。 Evaluation Sample の定義を見てみると An evaluation sample is a single structured data instance that is used to asses and measure the performance of your LLM application in specific scenarios. とあり、翻訳すると以下の感じです。 評価サンプルは、特定のシナリオにおけるLLMアプリケーションのパフォーマンスを評価・測定するために使用される単一の構造化データインスタンスです。 要は、評価・測定するために使うデータの入れ物という事をいってます。 使い方としては以下のような感じです。 sample1 = SingleTurnSample(     user_input="日本の首都はどこですか?", #質問     retrieved_contexts=["東京(とうきょう、英: Tokyo)は、関東平野の南部に位置し、東京湾に面する都市。日本の首都である."], #RAGが外部DBから取得し、参考にした内容     response="日本の首都は東京です.", #RAGから返された回答内容     reference="東京", #user_inputに対する真の答え ) 「ん?さっき話に出してた EvaluationDataset はどこいった?」という感じなのですが Evaluation Datasetの定義を見てみると An evaluation dataset is a homogeneous collection of data samples とあり、Evaluation Sampleのコレクションである事がわかります。 実際サンプルコードなどを見ると以下のような感じで、コレクションの関係があることが分かります。 dataset = EvaluationDataset(samples=[sample1, sample2, sample3]) メトリクスの追加 これまで使われていたメトリクス(評価指標)は引き続き使えることに加えて、いくつか新たなメトリクスも加わっています。 ドキュメント を見ると、RAGの評価にとどまらずAgentや、自然言語の評価も行えるようになってますね。  RAG以外のメトリクスも非常に気になりますが、今回はひとまずRAGの評価の言及にとどめたいと思います。 このメトリクスの追加に合わせて、メトリクスの設定方法も若干変わっています。 メトリクスの設定方法の変更 以前は以下のような形で、利用するメトリクスを設定していました。(必要な部分のみ抜粋) # import from ragas.metrics import faithfulness # list of metrics we're going to use metrics = [     faithfulness ] これが0.2.xのバージョンでは以下のように実装します。 メトリクスを選択する段階で利用するLLMを指定することを推奨するようです。 from ragas.metrics import Faithfulness faithfulness_metric = Faithfulness(llm=your_evaluator_llm) これにより、 どのメトリクスで、どのLLMを使うのかを明確にすることができる ようになりました。 評価方法の変更 また評価を行う部分の関数にも、いくつか変更が見られました。 今回はAzure OpenAI Serviceにデプロイしたモデルを使用する想定で実装します。 evaluate関数 まず、以前は以下のようにevaluate関数を使って評価を行っていました。 # import from ragas.metrics import faithfulness # list of metrics we're going to use metrics = [     faithfulness ] os.environ["AZURE_OPENAI_API_KEY"] = os.getenv("AZURE_OPENAI_API_KEY") os.environ["AZURE_OPENAI_ENDPOINT"] = os.getenv("AZURE_OPENAI_ENDPOINT") os.environ["OPENAI_API_VERSION"] = os.getenv("OPENAI_API_VERSION") generator_llm = AzureChatOpenAI(     azure_deployment="gpt-35-turbo-16k", ) embeddings = AzureOpenAIEmbeddings(     azure_deployment="text-embedding-ada-002" ) result = evaluate(     ds, metrics=metrics, llm=chat_llm, embeddings=embeddings ) ただ先程述べたように metrics の生成方法が変わってるので、そのあたりの影響を受けてきます。 0.2.x系では以下のように書きます。 from ragas.metrics import Faithfulness os.environ["OPENAI_API_KEY"] = os.getenv("AZURE_OPENAI_API_KEY") #なぜかAZURE_OPENAI_API_KEYという環境変数名にすると反応してくれなかった。 os.environ["AZURE_OPENAI_ENDPOINT"] = os.getenv("AZURE_OPENAI_ENDPOINT") os.environ["OPENAI_API_VERSION"] = os.getenv("OPENAI_API_VERSION") evaluator_llm = LangchainLLMWrapper(AzureChatOpenAI(     azure_deployment="gpt-4o-mini",     temperature=0, )) metrics = [     Faithfulness(llm=evaluator_llm) ] results = evaluate(dataset=eval_dataset, metrics=metrics) df = results.to_pandas() もう少し深掘りしてみていきたいと思います。 0.2.x系でのevaluate()のソースコードは以下のようになっています。 def evaluate(     dataset: t.Union[Dataset, EvaluationDataset],     metrics: list[Metric] | None = None,     llm: t.Optional[BaseRagasLLM | LangchainLLM] = None,     embeddings: t.Optional[BaseRagasEmbeddings | LangchainEmbeddings] = None,     callbacks: Callbacks = None,     in_ci: bool = False,     run_config: RunConfig = RunConfig(),     token_usage_parser: t.Optional[TokenUsageParser] = None,     raise_exceptions: bool = False,     column_map: t.Optional[t.Dict[str, str]] = None,     show_progress: bool = True, 主な変更点としては datasetとして EvaluationDataset が追加されている metricsの中身の Metrics の中身が変更されている llm, embeddingsに関してBaseRagasLLM, BaseRagasEmbeddingsというものが追加されている といった点が挙げられるかと思います。 先程Metricsごとに利用するLLMを指定できるといった説明をしましたが、仮に指定しない場合はevaluate関数で指定したLLMが使われるようです。 なお、優先度としてはevaluate関数の指定よりもMetricsの指定が高く、MetricsのLLMで上書きされるようです。 余談 なぜか0.2.x系になってからAzureOpenAIServiceにおける利活用方法がドキュメントから抹消されていました。(Bedrockはあるのに…) 「まぁでもLangchainLLMWrapperを挟んで使うし、LangcainでAOAIが使えれば問題ないだろう」と思って試してみたのですが なぜか[“AZURE_OPENAI_API_KEY”]という環境変数を読み込んでくれませんでした。 それで[“OPENAI_API_KEY”]という環境変数にAOAIのAPI KEYを入れたら動いてくれたので、今回はそういった実装にしてます。 ライブラリ側の問題なのか、自分のソースコードが問題なのか分からないですが、今回はそんなワークアラウンドで実装してます。 ascore関数 ascoreと呼ばれる関数にも変更が見られました。 Second is that metrics.ascore is now being deprecated in favor of metrics.single_score . You can make the transition as such とあるので、 ascore というメソッドが非推奨となり、そのかわり single_score を使ってくれ。ということです。 何のことかというと、以前書いたRagasの記事では、先程のようにevaluate()という関数を使って、複数のmetricsを一気に評価していました。 一方で、ascoreといったメソッドを使うことで、1つのデータに対して評価する方法も存在します。 このascoreという関数が0.2.xでは推奨されなくなり、 single_turn_ascore や multi_turn_ascore といった関数の使用が推奨されています。 このSingleやらMultiやらの話は後ほどしますので、とりあえず一旦使ってみます。 以下に実装のサンプルを載せます。 こちらのサンプルはとりあえず同期処理として single_turn_score を使っています。 os.environ["OPENAI_API_KEY"] = os.getenv("AZURE_OPENAI_API_KEY") os.environ["AZURE_OPENAI_ENDPOINT"] = os.getenv("AZURE_OPENAI_ENDPOINT") os.environ["OPENAI_API_VERSION"] = os.getenv("OPENAI_API_VERSION") evaluator_llm = LangchainLLMWrapper(AzureChatOpenAI(     azure_deployment="gpt-4o-mini",     temperature=0, )) single_turn_sample = SingleTurnSample(     user_input="日本の首都はどこですか?", #質問     retrieved_contexts=["東京(とうきょう、英: Tokyo)は、関東平野の南部に位置し、東京湾に面する都市。日本の首都である."], #RAGが外部DBから取得し、参考にした内容     response="日本の首都は東京です.", #RAGから返された回答内容     reference="東京" #user_inputに対する真の答え ) faithfulness = Faithfulness(llm=evaluator_llm) metrics = [     Faithfulness(llm=evaluator_llm) ] result = faithfulness.single_turn_score(single_turn_sample) # score = 1.0 メトリクスの分類 先程single_turnやmulti_turnといったものが出てきましたので、これについて見ていきたいと思います。 そもそも今回メトリクスは 評価にLLMを利用するか SingleTurnか、MultiTurnか の2軸で分類され、合計4パターンで分類されます。 Ragasの 公式 にもメトリクスの分類が分かりやすくまとめてありました。 この分類に合わせ、実装を切り替える必要があります。 Faithfulnessのメトリクスを例に、定義を見てみたいと思います。 @dataclass class Faithfulness(MetricWithLLM, SingleTurnMetric):     name: str = "faithfulness"  # type: ignore と書かれています。 MetricWithLLMを継承しているため、このFaithfulnessの評価にはLLMが利用されることが分かります。 そのため、Faithfulnessのコンストラクタ引数としてはLLMを与えてあげる必要があります。 他にはMetricWithEmbeddingsを継承しているメトリクスもありますが これは評価の際にEmbeddingsモデルを利用することを表します。 また、FaithfulnessのはSingleTurnMetricというクラスも継承しています。 これはユーザーとAI間の1回のやり取り(Interaction)に基づき評価を行うことを表します。 SingleTurnMetricを継承しているMetricsに関しては single_turn_ascore や、 single_turn_score のメソッドを利用して評価を行います。 Agentの評価に用いるMetricsだとMulti Turnなものが多いかもしれないですね。 テストセット生成のコスト効率改善 テストセットの仕組みが変わったようなのですが、すいません、正直まだ理解しきれていません…。 どうやらKnowledge Graphを使うようになったりして、大幅にコスト削減できるようになったようです。 このあたりは別の機会にまた試してみたいと思います。 プロンプトオブジェクトの変更 最後、プロンプトオブジェクトの変更についてです。 まず、変更点を先に述べると0.1.xでは Prompt というオブジェクトが利用されていましたが  0.2.xでは PydanticPrompt というオブジェクトが使われるようになりました。 ただ恥ずかしながら、自分もこの変更点見るまではRagasにおける「プロンプト」といったものを認識しておらず 「Ragasの中でそんなプロンプトを意識するところあるの?」といった感じでした。 それでソースコードを見てみると、色々なところで使われてたので少しご紹介します。 先程のFaithfulnessの例を挙げます。 Faithfulnessのコンストラクタの中に PydanticPrompt で定義されたプロンプトオブジェクトが含まれていました。 class Faithfulness(MetricWithLLM, SingleTurnMetric):     name: str = "faithfulness"  # type: ignore     _required_columns: t.Dict[MetricType, t.Set[str]] = field(         default_factory=lambda: {             MetricType.SINGLE_TURN: {                 "user_input",                 "response",                 "retrieved_contexts",             }         }     )     nli_statements_message: PydanticPrompt = field(default_factory=NLIStatementPrompt) #←ここ!!     statement_prompt: PydanticPrompt = field(default_factory=LongFormAnswerPrompt) #←ここ!!     sentence_segmenter: t.Optional[HasSegmentMethod] = None     max_retries: int = 1     _reproducibility: int = 1 今回デフォルトで使われている NLIStatementPrompt と LongFormAnswerPrompt の2つについて、それぞれの中身を確認してみました。 英語かつ少々長いですが、一番分かりやすい点として instruction の文章を確認してみます。 instructionはいわばAIへの命令文です。 まずはNLIStatementPromptのほうから。 Your task is to judge the faithfulness of a series of statements based on a given context. For each statement you must return verdict as 1 if the statement can be directly inferred based on the context or 0 if the statement can not be directly inferred based on the context. 翻訳すると以下の感じです あなたの仕事は、与えられた文脈に基づいて一連の声明の忠実さを判断することです。各声明に対して、文脈に基づいて直接推論できる場合は1、文脈に基づいて直接推論できない場合は0の判決を返さなければなりません。 次に、LongFormAnswerPromptの方は以下の内容でした。 Given a question, an answer, and sentences from the answer analyze the complexity of each sentence given under ‘sentences’ and break down each sentence into one or more fully understandable statements while also ensuring no pronouns are used in each statement. Format the outputs in JSON. “質問、回答、および回答からの文が与えられた場合、’sentences’ にある各文の複雑さを分析し、各文を一つ以上の完全に理解可能なステートメントに分解してください。また、各ステートメントには代名詞を使用しないようにしてください。出力はJSON形式でフォーマットしてください。 これを見てると、Faithfulnessの算出方法が頭をよぎります。 Faithfulnessのスコア算出方法は、以下の2ステップです。 得られた回答(response)を分解 分解された回答のなかで外部から取得した情報(retrieved_contexts)から導かれた個数/step1で分解された数 まさに、STEP1に該当するのが LongFormAnswerPromp で、STEP2に該当するのが NLIStatementPrompt と分かります。 つまり、最終的なFaithfulnessの算出に必要な要素を、これらのプロンプトを使って算出していることが読み取れました。 このようにRagasの内部で評価を行う際にも、プロンプトエンジニアリングのテクニックは活用されていることが分かりました。 そして、もちろんこのプロンプトの内容を自分で書き換えること(=新たなPydanticPromptを作成し、置き換えること)も可能です。 ただ、LLMを評価するためのRagasの中で、評価するためのプロンプトを書き直すとなってくると、そのプロンプトが適切か、かみたいな話にもなってきますね… 一般的なソフトウェアのテストにおいても、「テストコードが正しいことはどうやって証明しますか?」といった議題が上がりますが それに似た、もしくはそれ以上にややこしい問題な気もします。 まとめ 今回はRagasの0.2.x系のバージョンアップ内容をさらっとおさらいしてみました。 おさらいする中で知らなかった事も多々あったので、そのあたりは改めて勉強して記事にしていきたいと思います。 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【v0.2】Ragasのバージョンアップ内容のおさらい【Azure OpenAI Service】 first appeared on SIOS Tech. Lab .
はじめに こんにちはサイオステクノロジーの小野です。 前回 はOpenShiftにGPUを追加する方法について解説しました。今回はそのGPUをクラスタ内で利用する設定の方法を解説します。 また、GPUはいくつかの会社が作成していますが、多くのAI開発ライブラリが対応しているNVIDIA製のGPUの設定を行います。 OpenshiftでGPUを利用するためのオペレータ Node Feature Discovery (NFD) オペレータ クラスター内のノードのハードウェア機能やカーネル機能を自動的に検出し、それらの機能をラベルとしてノードに追加するオペレータです。これによりノードにGPUがあることを検出できます。NVIDIA GPUオペレータを利用するために必要なオペレータです。 NVIDIA GPU オペレータ NVIDIA GPUを搭載したサーバーでGPUリソースを管理し、活用できるようにするためのオペレータです。 Intel Device Plugins オペレータ Intelのハードウェア機能をOpenshiftクラスタに認識させるために使用されるオペレータです。IntelのGPUを利用する際はこのオペレータを使用してください。 AWSにおけるGPU利用設定手順 設定手順概要 これからAWSに構築したOpenshiftでNVIDIAのGPUを利用する方法を解説します。手順は以下の流れで進んでいきます。 NFDオペレータの設定 NFDオペレータの正常性確認 NVIDIA GPU オペレータの設定 NVIDIA GPU オペレータの正常性確認 GPUモニタリングの設定 前 提条件 OpenShift:4.17 構築する環境:AWS(amd64) リージョン:us-east-2 クラスター名:ocp-test AWSにNVIDIAのGPUノードを追加済み NFDオペレータの設定 NodeFeatureDiscoveryオペレータのインストール 最初にOperator > OperatorHubでNFDと検索します。 OperatorHubでNFDと検索 Node Feature Discovery Operatorが2つ存在するのでRed Hatと書かれている方を選択して、インストールを押します。 NFD Operatorの説明 インストール時の設定は何も変更せず、インストールを押します。 NFDのインストール設定 インストールが完了するまで待ちます。 ステータスがSucceededになったらインストール完了です。 NFDのステータスがSucceededになったらインストール完了 CRの設定 (NodeFeatureDiscovery) 続いてカスタムリソースの設定を行います。インストール済みOperatorの画面からNFDオペレータをクリックします。 NFDオペレータの詳細画面に遷移するので、提供されるAPIからNodeFeatureDiscoveryのインスタンスの作成を押します。 NFDの詳細 NFDの設定は何も変更せず、作成を押します。 NFDのカスタムリソース編集 作成が完了するまで待ちます。 ステータスが「Conditions: Available Upgradeable」となっていれば作成完了です。 カスタムリソースのステータスがConditions: Available Upgradeableなら作成完了 NFDオペレータ正常性確認 NFDオペレータの導入によって、GPUノードにGPUのラベルが付与されていることを確認します。 以下のコマンドを入力します。 $ oc describe node | egrep 'Roles|pci' | grep -v master Roles:              worker                     feature.node.kubernetes.io/pci-1d0f.present=true Roles:              worker                     feature.node.kubernetes.io/pci-10de.present=true                     feature.node.kubernetes.io/pci-1d0f.present=true Roles:              worker                     feature.node.kubernetes.io/pci-1d0f.present=true Roles:              worker                    feature.node.kubernetes.io/pci-1d0f.present=true NFDオペレータの導入が正常に行われたら、GPUが搭載されたノードに「pci-10de」というラベルが付与されていることが確認できます。 NVIDIA GPU オペレータの設定 NVIDIA GPU オペレータのインストール 次にNVIDIA GPU オペレータのインストールを行います。Operator > OperatorHubでNVIDIAと検索します。 OperatorHubでNVIDIAと検索 NVIDIA GPU Operatorを選択してインストールを押します。 NVIDIA GPU Operatorの説明 インストール時の設定は何も変更せず、インストールを押します。 NVIDIA GPU オペレータのインストール設定 インストールが完了するまで待ちます。 ステータスがSucceededになったらインストール完了です。 NVIDIA GPU オペレータのステータスがSucceededになったらインストール完了 CRの設定 (ClusterPolicy) 続いてカスタムリソースの設定を行います。インストール済みOperatorの画面からNVIDIA GPU オペレータをクリックします。 NVIDIA GPUオペレータの詳細画面に遷移するので、提供されるAPIからClusterPolicyのインスタンスの作成を押します。 NVIDIA GPU オペレータの詳細 ClusterPolicyの設定は何も変更せず、作成を押します。 ClusterPolicyのカスタムリソース編集 作成が完了するまで待ちます。少し時間がかかるので注意してください(10分前後)。 ステータスが「State: ready」となっていれば作成完了です。 カスタムリソースのステータスがState: readyなら作成完了 NVIDIA GPU オペレータの正常性確認 GPUが動作するかの正常性の確認を行います。今回は2種類の正常性確認を行います。 サンプルアプリ実行 GPUを利用してベクトルの加算を行うサンプルアプリを実行します。(参考: https://docs.nvidia.com/datacenter/cloud-native/openshift/latest/install-gpu-ocp.html#running-a-sample-gpu-application ) 以下のコマンドを実行してpodを作成します。 $ cat << EOF | oc create -f - apiVersion: v1 kind: Pod metadata: name: cuda-vectoradd spec: restartPolicy: OnFailure containers: - name: cuda-vectoradd image: "nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0-ubi8" resources: limits: nvidia.com/gpu: 1 EOF pod/cuda-vectoradd created コンテナのログでサンプルアプリが動作していることを確認します。 $ oc logs cuda-vectoradd [Vector addition of 50000 elements] Copy input data from the host memory to the CUDA device CUDA kernel launch with 196 blocks of 256 threads Copy output data from the CUDA device to the host memory Test PASSED Done GPUの状態確認 GPUの状態を表示するコマンドを実行します。(参考: https://docs.nvidia.com/datacenter/cloud-native/openshift/latest/install-gpu-ocp.html#getting-information-about-the-gpu ) nvidia-gpu-operator プロジェクトに変更します。 $ oc project nvidia-gpu-operator Now using project "nvidia-gpu-operator" on server " https://api.ocp-test.cpawstest.ps.staging-test.sios.jp:6443 ". pod名を取得します。 $ oc get pod -owide -lopenshift.driver-toolkit=true NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nvidia-driver-daemonset-417.94.202410090854-0-n7xjw 2/2 Running 0 28m 10.130.2.9 ip-10-0-18-161.us-east-2.compute.internal <none> <none> 取得したpod内でnvidia-smiコマンドを実行します。GPUのメモリ使用量、使用率、温度が確認できます。 $ oc exec -it nvidia-driver-daemonset-417.94.202410090854-0-n7xjw -- nvidia-smi Mon Oct 28 08:36:36 2024 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 550.90.07 Driver Version: 550.90.07 CUDA Version: 12.4 | |-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| | 0 Tesla T4 On | 00000000:00:1E.0 Off | 0 | | N/A 26C P8 14W / 70W | 1MiB / 15360MiB | 0% Default | | | | N/A | +-----------------------------------------+------------------------+----------------------+ +-----------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| | No running processes found | +-----------------------------------------------------------------------------------------+ GPU のモニタリング設定 GPUの管理に必要なモニタリングの設定方法について説明します(参考: https://docs.nvidia.com/datacenter/cloud-native/openshift/latest/enable-gpu-monitoring-dashboard.html )。 GitHub の DCGM エクスポーター リポジトリから最新の NVIDIA DCGM エクスポーター ダッシュボードをダウンロードします。 $ curl -LfO https://github.com/NVIDIA/dcgm-exporter/raw/main/grafana/dcgm-exporter-dashboard.json % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 18114 100 18114 0 0 19498 0 --:--:-- --:--:-- --:--:-- 19498 namespaceにダウンロードしたファイルからconfigmapを作成します。 $ oc create configmap nvidia-dcgm-exporter-dashboard -n openshift-config-managed --from-file=dcgm-exporter-dashboard.json configmap/nvidia-dcgm-exporter-dashboard created configmapにラベルをつけて、管理者のコンソールでGPUのダッシュボードを確認できるようにします。 $ oc label configmap nvidia-dcgm-exporter-dashboard -n openshift-config-managed "console.openshift.io/dashboard=true" configmap/nvidia-dcgm-exporter-dashboard labeled 同様に開発者のコンソールでGPUのダッシュボードを確認できるようにします。 $ oc label configmap nvidia-dcgm-exporter-dashboard -n openshift-config-managed "console.openshift.io/odc-dashboard=true" configmap/nvidia-dcgm-exporter-dashboard labeled 作成されたリソースを表示し、ラベルがつけられていることを確認します。 $ oc -n openshift-config-managed get cm nvidia-dcgm-exporter-dashboard --show-labels NAME DATA AGE LABELS nvidia-dcgm-exporter-dashboard 1 90s console.openshift.io/dashboard=true,console.openshift.io/odc-dashboard=true コンソール画面のモニタリング > ダッシュボードに移動し、ダッシュボードのリストから「NVIDIA DCGM Exporter Dashboard」を選択します。 GPUの各種情報をコンソール画面でモニタリングできるようになりました。 GPUのモニタリング画面 最後に NFDオペレータとNVIDIAオペレータを導入することで、OpenShiftでGPUを取り扱えるようになりました。 これでようやくOpenShift AIを導入するための準備が完了しました。次回はいよいよOpenShift AIを導入する方法について解説したいと思います。 参考 NVIDIA GPU オペレータ: https://docs.nvidia.com/datacenter/cloud-native/openshift/latest/index.html Intel Device Plugins オペレータ: https://intel.github.io/intel-technology-enabling-for-openshift/development/device_plugins/README.html ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post OpenShiftのGPU設定の方法について first appeared on SIOS Tech. Lab .
こんにちは、 PS-SL アプリチーム新卒 1 年目の織田です。 今回はLLM開発向けのフレームワークであるLangChainのサンプルコードを作成してみました。 コチラ から参照できますので、よろしければご活用ください。 サンプルコードを作成した理由 LangChainは非常に多種多様な機能を提供しており、どこから触れば良いのか分からず困ってしまう方もいらっしゃると思います。 また、ネット上には既に多くの方がLangChainのサンプルコードをまとめていますが、その多くが古いバージョンのもので2024年現在は使えない、あるいは非推奨の機能を用いている場合があります。 また、私は業務上AzureOpenAIを利用することが多いのですが、サンプルコードの多くにOpenAI APIが用いられています。名前はほぼ同じなのですが、実際に利用する際には細かい違いがあり、修正の手間がとても煩わしいです。 そこで、今回はAzureOpenAIユーザ向けに2024年10月時点で利用可能な基本的な機能をサンプルコード化しました。 使用方法 今回はGithub上でコードを公開しているので、直接ダウンローしていただくかgit cloneをしてご利用ください。言語はPythonを用いているので、必要に応じてインストールをお願いします。(インストール手順もReadmeに記載済みです) また、先述の通りAzureOpenAI(AOAI)リソースが必要になりますので、アカウント発行やリソース作成なども必要に応じて実施してください。 GPTのバージョンによって使用できる機能に制限がありますが、その点も調査・補足してあります。 試用可能な機能 今回サンプルコード化した機能は各種AOAIリソースを呼び出すための初歩的なものや、 OSC広島 の際にキャッチアップを進めたコードなどが中心になります。 これらの機能は今後のバージョンアップに伴ってなるべく更新をかけていきたいと思います。また、より多くの機能をサンプルコード化できるよう頑張ります。   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post LangChainのサンプルコードを作ってみた first appeared on SIOS Tech. Lab .
こんにちは。サイオステクノロジー OSS サポート担当 山本 です。 今回も solr のお話です。 今回は前回まで使っていた demo をベースにして、フィールドの内容を変更した新しいコアを作ってみることで、コアを作る手順を確認しようかと思います。 ■おさらい:コア?フィールド? コアを作っていく前に、solr の基本的な部分の用語を再確認しておきましょう。 今回見る必要な範囲でいうと、大まかに以下のような感じです。 コア : 箱 (空間) のようなもの。一連の 設定の適用範囲 であり、また検索の際にはコアを指定して検索します。 ドキュメント :ユーザが登録したデータ。その コア の スキーマ で定義された フィールド に合わせた、 一連のデータの組 を1つの単位として登録されます。 フィールド : ドキュメント の構造を決定する要素。型 (数値か文字列か) や、必須であるかどうか、 解析を行うかどうか (また、その解析方法) などをフィールド毎に決定します。 スキーマ で設定します。 スキーマ : フィールド の名前や型や 解析方法 、フィールドで使用される型などの定義を行うための設定ファイル。 …これだけ言われてもピンと来ないかもしれませんので、この先やってみながら確認していきましょう。 ■コアを作る ということで新しいコアを作ってみます。 一つの solr で複数のコアを作ることができる ので、今回は前回までにも使用していたデモ用の環境にコアを追加してみます。 …と軽く言ってはみますが、 solr のコアを作るには準備が必要 です。コマンド一つでお手軽簡単!!とはいきません。 用意するファイルの数もそこそこあるので、今回はフィールドの内容を決定する スキーマファイル のみ作成し、他の必要なファイルはデモ用環境のコア “demo” のものを流用してコアを作っていきます。 ■デモ環境 (コンテナ) の起動 まずはベースとなる solr 環境として、公式コンテナイメージからデモ用の環境を作ります。 これは前回まででやったのと同じです。podman を導入して以下のコマンドを実行すれば OK ですね。 ## 前回の環境を消してしまっている場合 $ podman run -dt --name test-solr -p 8984:8983 solr solr-demo ## 前回の環境から続けて試す場合 $ podman start test-solr ブラウザを開いて以下の URL にアクセスし、管理画面が開けることを確認できたら一旦 OK です。 http://(IPアドレス or ホスト名):8984/solr ■スキーマファイルの作成 続いて、 今回のお話のキー である スキーマ を作成します。 スキーマは、 “managed-schema.xml” (または “schema.xml”) という名前の xml ファイル で定義していきます。 今回は日本語を扱う 2つのフィールド “test_1” “test_2” と、managed-schema.xml で最低限必要になるフィールドだけを持ったスキーマファイルを作ってみます。 適当なテキストエディタ (vi など) を使って、以下のような内容を持つ “ managed-schema.xml ” を作成してください。 <?xml version="1.0" encoding="UTF-8"?> <schema name="default-config" version="1.6"> <types> <fieldType name="booleans" class="solr.BoolField" sortMissingLast="true" multiValued="true"/> <fieldType name="pdates" class="solr.DatePointField" docValues="true" multiValued="true"/> <fieldType name="plongs" class="solr.LongPointField" docValues="true" multiValued="true"/> <fieldType name="pdoubles" class="solr.DoublePointField" docValues="true" multiValued="true"/> <fieldType name="pint" class="solr.IntPointField" docValues="true"/> <fieldType name="plong" class="solr.LongPointField" docValues="true"/> <fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true"/> <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100" multiValued="true"> <analyzer type="index"> <tokenizer name="standard"/> <filter ignoreCase="true" words="stopwords.txt" name="stop"/> <filter name="lowercase"/> </analyzer> <analyzer type="query"> <tokenizer name="standard"/> <filter ignoreCase="true" words="stopwords.txt" name="stop"/> <filter ignoreCase="true" synonyms="synonyms.txt" name="synonymGraph" expand="true"/> <filter name="lowercase"/> </analyzer> </fieldType> <!-- ここまで必須分 --> <!-- ここから独自フィールド用の設定 --> <fieldType name="text_ja" class="solr.TextField" autoGeneratePhraseQueries="false" positionIncrementGap="100"> <analyzer> <tokenizer mode="search" name="japanese"/> <filter name="japaneseBaseForm"/> <filter tags="lang/stoptags_ja.txt" name="japanesePartOfSpeechStop"/> <filter name="cjkWidth"/> <filter ignoreCase="true" words="lang/stopwords_ja.txt" name="stop"/> <filter name="japaneseKatakanaStem" minimumLength="4"/> <filter name="lowercase"/> </analyzer> </fieldType> </types> <fields> <field name="_version_" type="plong" indexed="false" stored="false"/> <field name="id" type="string" multiValued="false" required="true" stored="true"/> <!-- ここまで必須分 --> <!-- ここから独自フィールド用の設定 --> <field name="test_1" type="text_ja"/> <field name="test_2" type="text_ja"/> </fields> <uniqueKey>id</uniqueKey> </schema> これでスキーマファイルの準備は OK です。 ■スキーマファイルの中身の基本的な見方 折角なので、今回のスキーマファイルの設定内容についても一部確認してみましょう。 まずざっくり見ると、 “<field>” と “<fieldType>” という要素を大量に定義してるなー、というのがわかるかと思います。 “<field>” 要素の方から確認してみましょう。 <fields> <field name="_version_" type="plong" indexed="false" stored="false"/> <field name="id" type="string" multiValued="false" required="true" stored="true"/> <!-- ここまで必須分 --> <!-- ここから独自フィールド用の設定 --> <field name="test_1" type="text_ja"/> <field name="test_2" type="text_ja"/> </fields> よくよく見てみると、 name 属性に今回作ると宣言していたフィールド名 “test_1” “test_2” を含むものがありますね。 お察しのとおり、 “<field>” 要素こそがこのスキーマの、つまりはこのスキーマを使うコアで扱えるフィールド を定義するための要素になります。 例えば、この行は “_version_” という名前のフィールドを定義しています。 <field name="_version_" type="plong" indexed="false" stored="false"/> ところで、<field> 要素の設定には name 属性の他に type 属性 が必ず含まれているのに気づくかと思います。 この “_version_” フィールドでは type は “plong” ですね。 ここで “<fieldType>” 要素に目を向けてみましょう。 <fieldType name="plong" class="solr.LongPointField" docValues="true"/> 探してみると、<fieldType> の中にはこのように name 属性が先の “plong” と同じ名前になっているものが存在しているはずです。 これは勿論偶然などではなく、”_version_” フィールドで使用される “plong” type がどんなものなのかを定義 するものになります。 実際にどんなものなのかはこの <fieldType> 要素の各属性を確認する必要がありますが、特に重要なのは class 属性です。 基本的に使用される class 属性の値については、 こちらのドキュメント を確認してください。例えば、ここで使用されている “solr.LongPointField” なら Long 値 (64bit 符号なし整数) ということになります。 もう一つ見てみましょう。”test_1″ “test_2” フィールドの設定部分です。 <field name="test_1" type="text_ja"/> <field name="test_2" type="text_ja"/> これらのフィールドで使用している type “text_ja” の定義を確認してみます。 <fieldType name="text_ja" class="solr.TextField" autoGeneratePhraseQueries="false" positionIncrementGap="100"> <analyzer> <tokenizer mode="search" name="japanese"/> <filter name="japaneseBaseForm"/> <filter tags="lang/stoptags_ja.txt" name="japanesePartOfSpeechStop"/> <filter name="cjkWidth"/> <filter ignoreCase="true" words="lang/stopwords_ja.txt" name="stop"/> <filter name="japaneseKatakanaStem" minimumLength="4"/> <filter name="lowercase"/> </analyzer> </fieldType> ちょっと長いですが、これ全部で fieldType “text_ja” の定義になります。 これは 前回 に長々とお話しした、solr の 文字列解析 を行うための設定をしたものになります。 class 属性に “solr.TextField”、子要素に <analyzer> を設定することで、この fieldType が解析処理を行う対象であることを定義 できます。 解析時の処理内容としては、<analyzer> の子要素として処理の内容を上から順番に書いていきます。 上記の例の場合だと、tokenizer の後に “japaneseBaseForm”、”japanesePartOfSpeechStop”、……という順番でフィルタ処理を行う形になります。 <analyzer> の中身についての詳細は、 こちらのドキュメント を確認してください。 なお、今回作ったスキーマファイルは非常にコンパクトな内容です。 色々と設定されている例が見たい場合は、デモ環境のデフォルトコア demo のスキーマファイルを覗いてみるといいかもしれません。 ■必要になるディレクトリ作成とファイル配置 ではコア作成の準備の仕上げとして、ディレクトリを作成してファイルを配置していきます。 とりあえず今回はデモ環境のコンテナ内に入って操作していきます。 $ podman exec -it test-solr /bin/bash まず、 環境変数 “ SOLR_HOME ” で設定されている solr のホームディレクトリ (デモ環境では “/var/solr/data/” になっているはずです) に、新しいコア用のディレクトリを作成します。 ここで作成する ディレクトリ名 がそのまま 新しいコアのコア名 になります。今回は “test-new-core” というコア名にしてみます。 (※コンテナ内での操作) $ mkdir /var/solr/data/test-new-core 続いて、必要になるファイルをこのディレクトリに配置していきます。 先述のとおり、今回は managed-schema.xml 以外はデモ環境 “demo” から流用するので、流用するファイルをコピーしていきます。 (実際に運用する環境を構築する際には、これらのファイルもコピーではなくちゃんと確認して設定しましょう。特に solrconfig.xml や solr.xml など) (※コンテナ内での操作) $ mkdir /var/solr/data/test-new-core/data $ mkdir /var/solr/data/test-new-core/conf $ mkdir /var/solr/data/test-new-core/conf/lang $ cp /var/solr/data/demo/core.properties /var/solr/data/test-new-core/ $ cp /var/solr/data/demo/conf/solrconfig.xml /var/solr/data/test-new-core/conf/ $ cp /var/solr/data/demo/conf/stopwords.txt /var/solr/data/test-new-core/conf/ $ cp /var/solr/data/demo/conf/synonyms.txt /var/solr/data/test-new-core/conf/ $ cp /var/solr/data/demo/conf/lang/stoptags_ja.txt /var/solr/data/test-new-core/conf/lang/ $ cp /var/solr/data/demo/conf/lang/stopwords_ja.txt /var/solr/data/test-new-core/conf/lang/ (※ コンテナ内の操作終わり) $ exit 最後に、作成した managed-schema.xml を配置すればファイルの配置は完了です。 $ podman cp ./managed-schema.xml test-solr:/var/solr/data/test-new-core/conf/managed-schema.xml ■コア作成の実行 必要な各種ファイルの配置ができたら、コア作成を実行します。 コマンドからでも Web の管理画面からでもコアは作成できます が、今回は Web の管理画面で追加する方法を見ていきます。 まずは適当なブラウザから管理画面にアクセスします。今回のコンテナの実行方法なら以下のアドレスですね。 http://(IPアドレス or ホスト名):8984/solr 管理画面にアクセスできたら、画面左メニューから “ Core-Admin ” を開きます。 “Add Core” から、”name” と “instanceDir” に 先の手順で作成した コア用のディレクトリと同じ名前 に変更して “Add Core” ボタンで… 新しいコアが登録できました。 画面左メニューの “Core Selector” プルダウンにも、新しいコアが出てくるようになっているはずです。 ■ドキュメントの登録と検索 新しくコアを作れたので、このコアを使って基本的なドキュメントの登録や検索の方法も確認しましょう。 ■ドキュメントの登録 ドキュメントの登録は、 Web 管理画面 または curl などを用いた直接のリクエスト により、 xml や JSON などの形式の ファイル または文字列の直接入力によって行うことができます。 今回は管理画面から xml ファイルを使って登録する方法を確認していきます。 まずは以下のような xml ファイルを作りましょう。(“<field> ” の中身はお好みの内容で OK です。) <add> <doc> <field name="test_1">サイオステクノロジー株式会社</field> <field name="test_2">SIOS Tech.Lab</field> </doc> <doc> <field name="test_1">OSSのsolrを試しています</field> <field name="test_2">ドキュメントの登録</field> </doc> <doc> <field name="test_1">これらの "doc" 内の "field" 要素は</field> <field name="test_2">スキーマで定義したフィールドに合わせて作りましょう</field> </doc> <doc> <field name="test_1">サンプルドキュメント</field> <field name="test_2">サンプルのドキュメント</field> </doc> <doc> <field name="test_1">OSSよろず相談室</field> <field name="test_2">some document</field> </doc> </add> 続いて、管理画面を開いて画面左メニューの “Core Selector” プルダウンから先ほど作ったコア名を選択し、” Documents ” を開きます。 “ Document Type ” で “ File Upload ” を選択したら、” Document(s) ” で先に作った xml ファイルを選択して “ Submit Document ” を押下すれば OK です。 ■ドキュメントの検索 続いて登録したドキュメントを検索してみましょう。 前々回もやった内容なので、ざっくりいきます。 まずは管理画面から登録に成功しているか確認してみます。 画面左メニュー “Core Selector” で先ほどのコア名を選択した状態で “ Query ” 画面を開き、 画面下 の “ Execute Query ” ボタンを押下します。 すると、登録したドキュメントが表示されるはずです。 この画面の “ q ” に、例えば “test_2:ドキュメント” や “test_2:ドキュメント AND test_1:OSS” などのように “ (フィールド名) : (検索文字列) ” という形式で条件を指定することで、検索条件を指定してドキュメントを表示させることもできます。 また、以下のような形式でアドレスに直接リクエストを行うことで、ドキュメントを取得することもできます。 http://(IPアドレス or ホスト名):(ポート)/solr/(コア名)/select?q=(条件) 例えば、curl を使って今回作った環境のドキュメントを取得すると以下のようになります。 ※ 記号や全角文字は URL エンコードする必要があります 。 $ curl 'http://(IP アドレス or ホスト名):8984/solr/test-new-core/select?q=test_1%3AOSS%20AND%20test_2%3Asome' { "responseHeader":{ "status":0, "QTime":2, "params":{ "q":"test_1:OSS AND test_2:some" } }, "response":{ "numFound":1, "start":0, "numFoundExact":true, "docs":[{ "id":"0fa6bbf2-b548-4933-b2de-107eebaea887", "test_1":"OSSよろず相談室", "test_2":"some document", "_version_":1814150309105106944 }] } } ■最後に 今回は solr に新しいコアを追加する手順を確認してみました。 今回はスキーマファイルのみ自作し、他の必要ファイルはデモ環境から流用するという実験用の手順大幅削減アプローチでやってみましたが、それでも確認すべきことは結構ありましたね。 勿論、今回流用した部分や見なかった部分にも確認すべき内容は多数あり、気軽に使うにはやや敷居が高いと感じるところはあります。 ただ、solr には活用することでより検索の利便性を向上させる機能がまだまだありますので、次回はそのうちの一つを紹介してみたいと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Solr って何者?③:コアを作ってみる first appeared on SIOS Tech. Lab .
はじめに こんにちはサイオステクノロジーの小野です。 前回 はOpenShift AIの概要について簡単に説明しました。今回はOpenShiftのノードの追加方法について解説します。OpenShift AIを利用するためには、GPUノードを追加する必要があるのでしっかりと操作を覚えましょう。 OpenShift(AWS)について OpenShiftはAmazon Web Services、Google Cloud Platform、IBM Cloud、Microsoft Azure のクラウド、vSphereなど多くのプラットフォーム上に簡単に構築することができます。AWS上で構築する際はEC2にノードがデプロイされるので、幅広いスペックを選べたり、簡単にサーバー移行が可能であったりといったメリットがあります。したがって、今回はAWS上にOpenShiftを構築してノードを追加します。 ノードの種類 マスターノード(コントロールプレーンノード) Kubernetesクラスターの管理と制御を行う役割を持つノードです。Kubernetesの特徴である負荷分散や高可用性はマスターノードによって管理されます。 ワーカーノード コンテナ化されたアプリケーションが動作するノードです。 インフラノード ルーティングやロギング、モニタリングなどのインフラストラクチャサービスを実行するために専用で用意するノードです。ワーカーノードと分けることでワーカーノードが落ちた場合やアップデートする場合にインフラを保つことができます。ちなみにインフラノードという用語はOpenShiftのドキュメントではよく出てきますが、Kubernetesのドキュメントには登場しません。 ノードの管理 マシンセット (MachineSet) 定義された数の同一のマシン(ノード)を保持し、管理するためのリソースです。マシンセットは、特定の設定を持つマシン(ノード)のレプリカを作成し、所望のレプリカ数を維持します。マシンセットは、ノードのスケーリングや自動修復(失敗したノードの置き換え)を行うために使用されます。 マシンコンフィグ(MachineConfig) マシンコンフィグはマシンの構成を定義します。 マシンコンフィグを使用することでクラスター内のノードに対して一貫した構成を適用できます。 マシンコンフィグプール (MachineConfigPool) マシンコンフィグプールは、特定のマシンコンフィグを適用するノードのグループを定義します。 マシンコンフィグプールを使用すると、異なるタイプや設定を持つノードの集まりをより簡単に管理できます。例えば、一部のノードはGPUを搭載しているが、他のノードは搭載していない、といった場合に便利です。 AWSでのノードの追加手順 OpenShiftのノード追加の方法についてこれから解説します。マシンセットを設定することで簡単にノードの追加が可能です。なお、今回行う手順(高いインスタンス等を設定)では高額な利用料金が発生する場合があるのでご注意ください。 前提条件 OpenShiftをIPIで構築した直後の状態から操作します。 OpenShift:4.17.1 構築する環境:AWS(amd64) リージョン:us-east-2 クラスター名:ocp-test cluster-admin権限があるユーザ操作 マシンセットの確認 コンソール画面のコンピュート > MachineSetsに移動します。 3つのマシンセットが存在することを確認します。 新規マシンセットの作成 MachineSetの作成を押します。 Red Hatのドキュメントに載っている サンプル を用いてマシンセットを作成します。 apiVersion: machine.openshift.io/v1beta1 kind: MachineSet metadata: labels: machine.openshift.io/cluster-api-cluster: <infrastructure_id> name: <infrastructure_id>-<role>-<zone> namespace: openshift-machine-api spec: replicas: 1 selector: matchLabels: machine.openshift.io/cluster-api-cluster: <infrastructure_id> machine.openshift.io/cluster-api-machineset: <infrastructure_id>-<role>-<zone> template: metadata: labels: machine.openshift.io/cluster-api-cluster: <infrastructure_id> machine.openshift.io/cluster-api-machine-role: <role> machine.openshift.io/cluster-api-machine-type: <role> machine.openshift.io/cluster-api-machineset: <infrastructure_id>-<role>-<zone> spec: metadata: labels: node-role.kubernetes.io/<role>: "" providerSpec: value: ami: id: ami-046fe691f52a953f9 apiVersion: machine.openshift.io/v1beta1 blockDevices: - ebs: iops: 0 volumeSize: 120 volumeType: gp2 credentialsSecret: name: aws-cloud-credentials deviceIndex: 0 iamInstanceProfile: id: <infrastructure_id>-worker-profile instanceType: m6i.large kind: AWSMachineProviderConfig placement: availabilityZone: <zone> region: <region> securityGroups: - filters: - name: tag:Name values: - <infrastructure_id>-worker-sg subnet: filters: - name: tag:Name values: - <infrastructure_id>-private-<zone> tags: - name: kubernetes.io/cluster/<infrastructure_id> value: owned - name: <custom_tag_name> value: <custom_tag_value> userDataSecret: name: worker-user-dataname: worker-user-data サンプルの変更点 基本的には既存のマシンセットを参考にして入力してください。 <infrastructure_id>:<クラスター名>-<ランダム文字列> infrastructure_idは以下のコマンドで確認するか、既存のマシンセットのyamlを見て確認してください。 $ oc get infrastructure cluster -o jsonpath='{.status.infrastructureName}' ocp-test-sp9lv <role>:worker <region>:us-east-2 <zone>:us-east-2a(リージョンとは別なので注意) metadata.name:マシンセットの名前(サンプルの通りに設定すると既存のマシンセットの名前と被ってエラーが出るので注意) instanceType:g4dn.xlarge(今回はGPUノードを追加することを考えるのでGPU用のインスタンスタイプを選択) securityGroupsのfliters:’tag:Name’ securityGroupsのvalues:<infrastructure_id>-node, <infrastructure_id>-lb(2つ設定する) ami.id:ami-048d893cb41cbd3cf(インスタンスタイプに対応したAMI) subnetのvalues:<infrastructure_id>-subnet-private-<zone> 変更後のyaml apiVersion: machine.openshift.io/v1beta1 kind: MachineSet metadata: labels: machine.openshift.io/cluster-api-cluster: ocp-test-sp9lv name: ocp-test-sp9lv-worker-us-east-2a-gpu namespace: openshift-machine-api spec: replicas: 1 selector: matchLabels: machine.openshift.io/cluster-api-cluster: ocp-test-sp9lv machine.openshift.io/cluster-api-machineset: ocp-test-sp9lv-worker-us-east-2a template: metadata: labels: machine.openshift.io/cluster-api-cluster: ocp-test-sp9lv machine.openshift.io/cluster-api-machine-role: worker machine.openshift.io/cluster-api-machine-type: worker machine.openshift.io/cluster-api-machineset: ocp-test-sp9lv-worker-us-east-2a spec: metadata: labels: node-role.kubernetes.io/<role>: "" providerSpec: value: ami: id: ami-048d893cb41cbd3cf apiVersion: awsproviderconfig.openshift.io/v1beta1 blockDevices: - ebs: iops: 0 volumeSize: 120 volumeType: gp2 credentialsSecret: name: aws-cloud-credentials deviceIndex: 0 iamInstanceProfile: id: ocp-test-sp9lv-worker-profile instanceType: g4dn.xlarge kind: AWSMachineProviderConfig placement: availabilityZone: us-east-2a region: us-east-2 securityGroups: - filters: - name: 'tag:Name' values: - ocp-test-sp9lv-node - filters: - name: 'tag:Name' values: - ocp-test-sp9lv-lb subnet: filters: - name: tag:Name values: - ocp-test-sp9lv-subnet-private-us-east-2a tags: - name: kubernetes.io/cluster/ocp-test-sp9lv value: owned userDataSecret: name: worker-user-data マシンセットの作成 サンプルの変更が完了したものを入力して作成を押します。 マシンセットが作成されたら、マシンの作成が始まるのでしばらく待ちます。 マシンセットのデプロイ 作成したMachineSetのMachineのメッセージで「Machine successfully created」が表示されたらマシン作成完了です。 作成したMachineSetのMachinesタブに移動して作成したMachineの欄にノードが表示されていて、ノードのステータスがReadyになっていればノードの追加完了です。 AWS画面での確認 AWSコンソールにログインし、リージョンをus-east-2に設定した後、EC2 > インスタンスのページを開きます。 OpenShiftのマシンセットを作成することで、 自動的にEC2のインスタンスが作成されていることが確認できます。 [画像] 注意事項 AMIのアーキテクチャがx86_64であるかarm64であるかによって使えるインスタンスタイプが変わるので注意してください。 AMIのアーキテクチャ確認 $ aws ec2 describe-images --image-ids ami-048d893cb41cbd3cf --query "Images[*].{Architecture:Architecture}" [ { "Architecture": "x86_64" } ] インスタンスタイプが対応しているアーキテクチャ確認 $ aws ec2 describe-instance-types --region us-east-2 --instance-types g4dn.xlarge --query "InstanceTypes[*].ProcessorInfo.SupportedArchitectures" [ [ "x86_64" ] ] AMIの起動モードがUEFIであるかlegacy-biosであるかによっても使えるインスタンスタイプが変わるので注意してください。 AMIの起動モード確認 $ aws ec2 describe-images --image-ids ami-048d893cb41cbd3cf --query "Images[*].BootMode" [ "legacy-bios" ] インスタンスタイプの対応している起動モード確認 $ aws ec2 describe-instance-types --region us-east-2 --instance-types g4dn.xlarge --query "InstanceTypes[*].SupportedBootModes" [ [ "legacy-bios", "uefi" ] ] 最後に AWS上にOpenShiftを構築してマシンセットを作成することで、GPUノードの追加ができました。以上の手順を参考にしてノード追加を行ってみてください。 最後に重要な注意事項として、GPUノードを追加しましたが、今の段階だとまだGPUがノードに認識されていません。したがってGPUを用いて機械学習を行うことができません。次回はノードにGPUを認識させるためにNode Feature Discovery OperatorとNVIDIA Operatorを導入する方法について解説します。お楽しみに。 参考 AWSインスタンスの料金表: https://aws.amazon.com/jp/ec2/instance-types/ アーキテクチャごとのAMIの設定: https://github.com/openshift/openshift-docs/blob/main/modules/installation-aws-user-infra-rhcos-ami.adoc https://b.chiroito.dev/entry/2020/04/23/173356 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post OpenShift(AWS)へのノード追加 first appeared on SIOS Tech. Lab .
CI/CD の導入は現代のアプリケーション開発において不可欠な要素となっています。 前回の記事 では、CI 部分を担う OpenShift Pipelines について解説しました。続いて、本記事では CD 部分を担う OpenShift GitOps について解説します。 CI/CD について 再度の記載になりますが、Continuous Integration / Continuous Delivery の略称で、継続的インテグレーション&継続的デリバリーという意味です。具体的に CI と CD が何を指すかというと以下の通りです。 CI は、ビルドとテスト等を自動化させることを指します。コード変更がメインブランチにマージされるたびに自動ビルドとテストが行われ、問題を早期に検出して対処することができます。 CD は、CI のプロセスをさらに拡張し、アプリケーションを自動的にリリース準備段階まで(継続的デリバリー)または本番環境にデプロイ(継続的デプロイメント)することを目指します。 前回の記事で解説した OpenShift Pipelines は CI 部分を担っていました。OpenShift Pipelines でも CD 部分は実装が可能ですが、Push 型のデプロイになってしまいました。OpenShift GitOps では開発環境や本番環境の状態を検知して、あるべき状態に変更する Pull 型の実装が可能なため、CI と CD は分離して CD 部分は OpenShift GitOps で実装すると様々なパターンに対して柔軟に対応できます。分離した構成図の例が下記になります。CI と CD が独立していますが、一つの CI/CD パイプラインとして動作可能です。 GitOps とは? GitOps とは、DevOps の概念をインフラ管理に適用した考え方です。Git リポジトリ内のマニフェストを正の状態としてインフラとアプリの状態を Git リポジトリと同期させる手法で、インフラストラクチャのコード化(Infrastructure as Code)と CI/CD を組み合わせたものになります。マニフェストを正の状態とすることで、 OpenShift 上のリソースが誤って削除された場合などに、Git リポジトリと同期してリソースを復元できたりもします。GitOps は大規模な共同作業が可能ですが万能なものではなく、直接環境を編集する場合に比べると、プルリクエストの作成やその承認などのプロセスは増えてしまいます。 OpenShift GitOps とは? Argo CD をベースにしており、Git リポジトリに格納されたマニフェストファイルを元に OpenShift のリソースを管理します。クラスタの状態を更新したい時は Git リポジトリに変更を加えるだけで自動的に更新することが出来ます。これによって、開発、ステージング、本番環境など異なる環境のクラスターへアプリケーションをデプロイする際に、一貫性を保ちながら効率化を図ることが出来ます。CD 部分以外にもデプロイメントと Kubernetes リソースの可視化などを実現できる点も利点となっています。 ベースとなっている Argo CD については 初心者でもわかる!Argo CDとは? という記事でデモまで行っているので、読んでもらえると動作のイメージをつかめると思います。 その他の CD ソリューションについて Spinnaker Spinnaker は、マルチクラウド環境でのアプリケーションデプロイが可能です。AWS、Google Cloud Platform、Azure など様々なクラウドプラットフォームに対応しており、柔軟なパイプライン構築や複雑なデプロイメント戦略にも対応しています。しかし、GUI で更新差分を見ることが出来ないため、直感的な見やすさでは ArgoCD に劣ります。 GitLab CI/CD GitLab リポジトリに統合されたCD ソリューションです。コードの変更をトリガーとして自動的にビルド、テスト、デプロイを行います。セルフホスト型とクラウド型の両方が利用可能で、多様な運用環境に対応しています。ArgoCD とは異なり、ソース管理と CI/CD が統合されており、一貫した管理が可能です。GitLab 以外のソース管理システムとの統合が難しい点が ArgoCD との違いになります。 まとめ CI/CD の導入は現代のアプリケーション開発において不可欠な要素となっており、更に関連技術で GitOps などの様々な概念も生まれています。本記事では CI/CD の振り返りと GitOps、 OpenShift GitOps について解説しました。次の記事では実際に OpenShift Pipelines の導入を行っていきたいと思います。 参考文献 https://www.redhat.com/ja/technologies/cloud-computing/openshift/gitops https://www.redhat.com/ja/topics/devops/what-is-gitops https://github.com/mamoru1112/openshift-gitops-handson https://spinnaker.io/ https://media.colorfulpalette.co.jp/n/n934459568564 https://gitlab-docs.creationline.com/ee/ci/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Red Hat OpenShift GitOps とは? first appeared on SIOS Tech. Lab .
はじめに 皆さんこんにちは。エンジニアの細川です。 皆さんはTypeScriptで開発を行っていたときに、型の特定のプロパティだけ必須にしたいと思ったことはありませんか? 例えば以下のようなHuman型を考えてみます。 type Human = { name: string; age: number; phoneNumber?: number email?: string; } このHuman型のphoneNumberを必須にした型をが欲しいときに、いちいち以下のように全プロパティを定義しなおすのは面倒です。 type HumanRequiredPhoneNumber = { name: string; age: number; phoneNumber: number email?: string; } このような場合に利用できるUtility型の作り方を紹介します。 指定したプロパティを必須にするユーティリティ型を作成 ある型の特定のプロパティを必須にするには以下のようなユーティリティ型を用意します。 /** * Tのうち、Kで指定されたプロパティを必須にする */ type MakeRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>; 使い方は以下の通りです。 先ほどと同じようにHuman型のphoneNumberを必須にしたい場合は以下のように書きます。 type HumanRequiredPhoneNumber = MakeRequired<Human, "phoneNumber"> このように第1引数に型名、第2引数に必須化したいプロパティ名を書くことで、以下のようにphoneNumberが必須になっていることを確認できます。 また、emailも必須にしたい場合は以下のように定義すればOKです。 type HumanRequired = MakeRequired<Human, "phoneNumber" | "email"> こうすると、phoneNumberに加えてemailも必須にすることができます。 null許容も除外したい場合 上記の型を利用すればoptionalなプロパティを必須にできるのですが、場合によってはnull許容のプロパティを必須にしたい場合もあるかと思います。 以下のような Human 型から HumanRequiredNotNull 型を作るような場合を考えます。 type Human = { name: string; age: number; phoneNumber?: number | null; email?: string | null; } ↓ type HumanRequiredNotNull = { name: string; age: number; phoneNumber: number; email: string } この場合は以下のようなユーティリティ型を用意します。 /** * Tのうち、Kで指定されたプロパティを必須にし、nullを除去する */ type MakeRequiredNonNullable<T, K extends keyof T> = Omit<T, K> & Required<{ [P in K]: NonNullable<T[P]> }>; 使い方は先ほどと同様に一つもしくは複数のプロパティを指定して、必須かつnull許容では無いプロパティにすることができます。 type HumanRequiredNotNull = MakeRequiredNonNullable<Human, "phoneNumber" | "email"> 実装してみると以下のようにエラーが出るようになります。 まとめ オプショナルなプロパティを必須にしたい場合は以下 /** * Tのうち、Kで指定されたプロパティを必須にする */ type MakeRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>; // 利用する場合 type HumanRequired = MakeRequired<Human, "phoneNumber" | "email"> const taro: HumanRequired = { name: "taro", age: 20, phoneNumber: 00000000000, email: "xxx@xxx.com" } null許容かつオプショナルなプロパティを必須にし、nullを除外したい場合は以下 /** * Tのうち、Kで指定されたプロパティを必須にし、nullを除去する */ type MakeRequiredNonNullable<T, K extends keyof T> = Omit<T, K> & Required<{ [P in K]: NonNullable<T[P]> }>; // 利用する場合 type HumanRequiredNotNull = MakeRequiredNonNullable<Human, "phoneNumber" | "email"> const taro: HumanRequiredNotNull = { name: "taro", age: 20, phoneNumber: 00000000000, email: "xxx@xxx.com" } おわりに 今回は既存の型の特定のプロパティのみを必須にする型を紹介しました。 パッケージなどによって作られる型などは特に再定義するのが面倒なことも多いと思うので少しでも皆さんの役に立てば幸いです。 ちなみに紹介した型の中で利用している、OmitやRequiredなどは事前にTypeScriptに用意されているユーティリティ型です。便利なものも多いので気になる方は こちら でどんな型があるか調べてみてください。 他にもTypeScriptのあれこれを紹介していますので、ぜひ 他の記事 も見てみてください!     ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post TypeScriptのあれこれ⑦ 型の特定のプロパティだけ必須化したい! first appeared on SIOS Tech. Lab .
こんにちは。サイオステクノロジーの和田です。今回はNFTの発行ができる簡単なアプリを作成したので共有したいと思います。 今回作るもの 今回はNFTを発行することができるWebアプリケーションを作成し、動作確認までしたいと思います。具体的にはウォレットアドレスとトークンIDを入力することで、NFTを発行できるようにしたいと思います。それではいきましょう。 作成手順 以下の手順で進めていきたいと思います。 スマートコントラクトの作成 バックエンドのコード作成 フロントエンドのコード作成 動作確認 スマートコントラクト作成 今回はRemixを使ってコントラクトを作成していきます。 以下のようにコントラクトを作成しました。 // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract MyToken is ERC721, Ownable { constructor(address initialOwner) ERC721("MyToken", "MTK") Ownable(initialOwner) {} function safeMint(address to, uint256 tokenId) public onlyOwner { _safeMint(to, tokenId); } } こちらのファイルをRemixの画面で作成します。まず、アイコンパネルからファイルを選択し、contractsフォルダの中に上記内容のファイルを作成します。 続いてファイルをコンパイルしていきます。コンパイルのアイコンをクリックしてコンパイル画面を表示します。青色のボタンをクリックすることで、コンパイルが完了します。 コンパイルが完了すると以下のように表示が追加されるので、ABIをコピーしておいてください。バックエンド実装で後に使用します。 続いてデプロイを行います。デプロイのアイコンをクリックしてデプロイ画面に行きます。ENVIRONMENTをMetaMaskに設定し、ネットワークはSepoliaにデプロイします。デプロイ時の初期値として自分のウォレットアドレスを入れておきます。 デプロイが完了すると、以下のようにDeployed Contractsが表示されるので、コントラクトアドレスをコピーしておきます。こちらも後でバックエンドの実装で使用します。 バックエンド実装 次に、バックエンドの実装をしていきたいと思います。今回はNest.jsを使用して作成しました。Nest.jsはNode.js用のフレームワークで、APIの作成を容易にすることができます。ControllerとServiceがあり、Controllerはルーティングを定義し、Serviceは実際の処理を記述します。今回のケースでは、NFTのMint処理を記述することになります。 nest generateコマンドでControllerとServiceを作ったので、以下に記載します。 ディレクトリ構造 nft ├── nft.controller.spec.ts ├── nft.controller.ts ├── nft.service.spec.ts └── nft.service.ts nft.controller.ts import { Controller, Post, Body } from '@nestjs/common'; import { NftService } from './nft.service'; @Controller('nft') export class NftController { constructor(private readonly nftService: NftService) {} @Post() async mint( @Body('accountAddress') accountAddress: string, @Body('tokenId') tokenId: string, ) { return await this.nftService.mintNft(accountAddress, tokenId); } } nft.service.ts import { Injectable } from '@nestjs/common'; import Web3 from 'web3'; import 'dotenv/config'; @Injectable() export class NftService { async mintNft(accountAddress: string, tokenId: string) { const network = process.env.ETHEREUM_NETWORK; const web3 = new Web3( new Web3.providers.HttpProvider( `https://${network}.infura.io/v3/${process.env.INFURA_API_KEY}`, ), ); const contractABI = [...]; // 作成したコントラクトのABI const contractAddress = String(process.env.CONTRACT_ADDRESS); // スマートコントラクトのアドレス const myContract = new web3.eth.Contract(contractABI, contractAddress); const privateKey = String(process.env.PRIVATE_KEY); // あなたのプライベートキー const data = myContract.methods .safeMint(accountAddress, tokenId) .encodeABI(); const tx = { from: accountAddress, to: contractAddress, data: data, gas: 100000, gasPrice: web3.utils.toWei('250', 'gwei'), }; try { const signed = await web3.eth.accounts.signTransaction(tx, privateKey); const receipt = await web3.eth.sendSignedTransaction( signed.rawTransaction, ); console.log('Transaction receipt:', receipt); return receipt; } catch (error) { console.error('NFTの発行に失敗しました:', error); throw error; } } } contractABIには先ほどスマートコントラクトの作成でコピーしたものを張り付けてください。 また、プロジェクトルートに.envファイルを配置して、以下のように設定しました。 .env PRIVATE_KEY="あなたのアカウントのプライベートキー" INFURA_API_KEY="インフラのAPIキー" ETHEREUM_NETWORK="sepolia" CONTRACT_ADDRESS="作成したスマートコントラクトのアドレス" 今回は Infura を使ってネットワークにアクセスしました。 Infuraでプロジェクトを作成し、APIキーを取得して貼り付けてください。ネットワークはスマートコントラクトをデプロイしたsepoliaとしています。 フロントエンド実装 続いて、フロントエンドの実装をしていきます。今回はNext.jsを用いてフロントを作成しました。下記のようなアドレスとトークンIDを受け取ってNFTを発行できるフォームを作りました。 "use client"; import React, { useState } from "react"; import { Button, TextField, Container, Box, Typography } from "@mui/material"; export default function FormComponent() { const [address, setAddress] = useState(""); const [tokenId, setTokenId] = useState(""); const [result, setResult] = useState(""); const handleSubmit = async (e: { preventDefault: () => void }) => { e.preventDefault(); try { const response = await fetch("<http://localhost:5000/nft>", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ accountAddress: address, tokenId: tokenId, }), }); if (!response.ok) { throw new Error(`Error: ${response.status}`); } // レスポンスデータを取得 const mintResult = await response.json(); console.log(mintResult); // 成功した場合、入力フィールドをクリアし、結果メッセージを設定 setAddress(""); setTokenId(""); setResult("登録成功"); } catch (error) { // エラー処理 console.error("Error minting NFT:", error); setResult("登録失敗"); } }; return ( <Container maxWidth="sm"> <Box display="flex" flexDirection="column" alignItems="center" justifyContent="center" height="100vh"> <Typography variant="h4" component="h1" gutterBottom> トークンの登録 </Typography> <form onSubmit={handleSubmit} style={{ width: "100%" }}> <Box mb={2}> <TextField fullWidth label="アドレス" variant="outlined" value={address} onChange={(e: { target: { value: React.SetStateAction<string> } }) => setAddress(e.target.value)} required /> </Box> <Box mb={2}> <TextField fullWidth label="トークンID" variant="outlined" value={tokenId} onChange={(e: { target: { value: React.SetStateAction<string> } }) => setTokenId(e.target.value)} required /> </Box> <Box width="100%" display="flex" justifyContent="center" mt={2}> {result} </Box> <Box width="100%" display="flex" justifyContent="center" mt={2}> <Button type="submit" variant="contained" color="primary" size="large"> 登録 </Button> </Box> </form> </Box> </Container> ); } それでは実際に動作確認していきたいと思います。フロントとバックをそれぞれ立ち上げて接続してみます。 以下のようにアドレスとトークンIDを入力します。ここで入力するアドレスはウォレットのアドレスを入力してください。 実行した結果、以下のように登録成功しました。 アカウントにNFTが発行されているのか、Metamaskで確認したいと思います。 画像のようにNFTをインポートをクリックして、NFTをインポートします。NFTをインポートする際に入れるアドレスはスマートコントラクトのアドレスを入れてください。インポートをクリックすると、NFTが無事追加されたことが確認できました。 まとめ 今回はNFTを発行することができる簡単なWebアプリケーションを作成してみました。実際に実装してみることで、NFTの仕組みをより深く理解することができました。今回は実装しませんでしたが、今後NFTの画像も表示できるように拡張してみたいと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post NFT発行アプリ作ってみた first appeared on SIOS Tech. Lab .
こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 2024/10/15、Cisco が不正アクセスがあった旨を公式発表し、DevHub ポータルを非公開にしました。 Cisco Event Response: Reports of Security Incident https://sec.cloudapps.cisco.com/security/center/resources/october_15_2024 2024/10/28~29、Open Source Summit Japan が東京で開催されます。 https://events.linuxfoundation.org/open-source-summit-japan/ https://events.linuxfoundation.org/open-source-summit-japan/ 2024/10/21、サイバートラスト社は SBOM に対応した Linux OS「Enterprise Pack for AlmaLinux」の提供を開始すると発表しました。 サイバートラスト、AlmaLinux に SBOM 対応の独自機能を追加した「Enterprise Pack for AlmaLinux」を提供開始 https://www.cybertrust.co.jp/pressrelease/2024/1021-enterprise-pack.html ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2024年10月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech. Lab .
今号は、前号の「 パッケージマネージャについて 」の続きになります! 今回は Debian 系システムの apt についてご紹介します。 yum/dnf と apt の違いは? Red Hat 系システムで使用される yum/dnf と Debian 系システムで使用される apt では、下記の様な違いがあります。 yum/dnf は RPM 形式のパッケージを使用するのに対し、apt は DEB 形式のパッケージを使用します。 yum/dnf は /etc/yum.repos.d 配下の repo ファイルを使用してリポジトリを管理するのに対し、apt は /etc/apt/sources.list.d 配下のファイルを使用してリポジトリを管理します。 yum/dnf は yum、dnf コマンドにサブコマンドを指定してパッケージ管理をするのに対し、apt は apt-get や apt-cache コマンドなど、様々なコマンドを使用してパッケージ管理をします (この点については次節で詳しく説明します)。 基本の操作方法 (検索、インストール、アップデート、削除) apt-get コマンドの基本操作 (パッケージの検索、インストール、アップデート、削除) について、それぞれ説明します。 検索 特定のパッケージを検索するには、 apt-cache コマンドを実行します。 例えば、git というパッケージを検索する場合は、下記の様なコマンドになります。 # apt-cache search git eject - Linux での CD のイジェクトおよび CD チェンジャー操作 freetype-doc - FreeType 2 font engine, development documentation fwupd - ファームウェア更新デーモン fwupd-doc - Firmware update daemon documentation (HTML format) gir1.2-geocodeglib-1.0 - geocode-glib ライブラリの introspection データ gir1.2-gxps-0.1 - GObject introspection data for the gpxs library git - 速く、スケーラブルな分散型リビジョン管理システム repo - repository management tool built on top of git steam-installer - Intaller for valve's Steam digital software delivery system w3-recs - Recommendations of the World Wide Web Consortium (W3C) xtrs - emulator for TRS-80 Model I/III/4/4P computers steam - valve's Steam digital software delivery system この時、結果には git という文字列を含むパッケージすべてと、要約 (説明文) が表示されます。 インストール パッケージをインストールするには、 apt-get install コマンドを実行します。 例えば、git というパッケージをインストールする場合は、下記の様なコマンドになります。 # apt-get install git パッケージリストを読みjんでいます... 完了 依存関係ツリーを作成しています... 完了 状態情報を読み取っています... 完了 … 以下のパッケージが新たにインストールされます: git git-man liberror-perl アップグレード: 0個、新規インストール: 3個、削除: 0個、保留: 592個。 4,146 kb のアーカイブを取得する必要があります。 この操作後に追加で 21.0 MB のディスク容量が消費されます。 続行しますか? [Y/n] この時、指定したパッケージのみではなく git と依存関係があるパッケージも併せてインストールされます。 なお、上記の様にインストールを開始する前に 続行しますか? [Y/n] の文字列が表示され、インストールを続行するかを聞かれます。 Y (もしくは y) を押下するとインストール続行、n を押下するとインストールを中止 します。 アップデート パッケージをアップデートするには、 apt-get upgrade コマンドを実行します。 例えば、既にインストール済みの apt というパッケージをアップデートする場合は、下記の様なコマンドになります。 # apt-get upgrade apt パッケージリストを読みjんでいます... 完了 依存関係ツリーを作成しています... 完了 状態情報を読み取っています... 完了 アップグレードパッケージを検出しています... 完了 … 以下のパッケージはアップグレードされます:  accountsservice alsa-ucm-conf amd64-microcode apparmor apport apport-gtk apt  apt-utils avahi-autolpd avahi-daemon avahi-utils base-files bash  bind9-dnsutils bind9-host bind9-libs bluez bluez-cups bluez-obexd brltty … (長いため省略) … アップグレード: 570個、新規インストール: 0個、削除: 0個、保留: 22個。 344 standard security updates 786 MB 中 682 MB のアーカイブを取得する必要があります。 この操作後に追加で 299 MB のディスク容量が消費されます。 続行しますか? [Y/n] 削除 パッケージをシステム上から削除するには、 apt-get remove コマンドを実行します。 例えば、既にインストール済みの git というパッケージを削除する場合は、下記の様なコマンドになります。 # apt-get remove git パッケージリストを読みjんでいます... 完了 依存関係ツリーを作成しています... 完了 状態情報を読み取っています... 完了 … 以下のパッケージは「削除」されます: git アップグレード: 0個、新規インストール: 0個、削除: 1個、保留: 592個。 この操作後に追加で 18.9 MB のディスク容量が解放されます。 続行しますか? [Y/n] apt-cache、apt-get コマンドについての補足事項 apt-get install コマンド実行時に対象パッケージが既にインストール済みである場合はアップデートが実行され、反対に apt-get upgrade コマンド実行時に対象パッケージがインストールされていない場合はインストールが実行されます。 特定のパッケージではなく、システム全体のパッケージをアップデートしたい場合は、パッケージの指定なしで apt-get upgrade コマンドを使用します。 ただし、このコマンドはカーネルやその他の重要なシステムコンポーネントをインストールしないため、これらを含めてアップグレードするには apt-get dist-upgrade コマンドを使用します。 特定のパッケージではなく、システム全体で他のどのパッケージからも必要とされていない (依存関係がない) パッケージを自動的に削除するために、 apt-get autoremove コマンドが使用できます。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 知っておくとちょっと便利!パッケージマネージャについて2 ~Debian 系システム編~ first appeared on SIOS Tech. Lab .
PS/SLの佐々木です。 今回はEthereumを使用して簡単なDID/VCを実現してみようと思います。 DID/VCとは DID/VCとは分散IDと検証可能なデジタル証明書のことを指します。 通常の認証とは異なり、認証(VCを検証)する際に発行元に問い合わせる必要がありません。 よって発行元がなくなってしまっても問題なく検証ができます。 また検証もDID Resolverを使用することで誰でも検証することができるため、特定の機関が発行した証明書(VC)を他サービスで検証することも容易です。 DIDとEthereumの関係性 ブロックチェーンの技術はDID/VCの文脈で相性が良くたびたび話題に出ますが、一体どこでブロックチェーンが使用されているのでしょうか? DID/VCの文脈でブロックチェーンが登場するときには検証用の公開鍵の保存先としてブロックチェーンが登場します。 例えば今回実装を紹介するEthereumでは EIP1056 という規格で規定されています。 よってVCの中身の情報であったり実際の検証に関してはブロックチェーンが登場することはありません。 ではなぜブロックチェーンと相性が良いのでしょうか? それはどちらも分散型である点が挙げられます。 例えばdid:webの場合特定のドメインに依存する形でDIDを解決をする必要があります。一方でdid:ethrを使用する場合には分散ネットワーク上にあるため世界中のどこの誰であってもアクセスすることができるためDID/VCの世界観により近いと言えます。 DID/VCの実装にあたって必要な予備知識 今回はタイトル通りdid:ethrを使用してDID/VCを実現します。 Ethereumで実現するためにはEIP1056というDIDを管理する標準的な仕様があります。 ERC1056は、W3Cが提案するDID仕様と互換性があり、Ethereumエコシステムの中で自己主権型IDを管理するための標準的な手段となっています。 このERC1056を実装するためのライブラリ、コントラクトとして以下のようなものが提供されており、今回はこれらに倣って実装していきます。 ethr-did-registry 公開鍵を保存したり、did-documentに表示される属性を更新できるようにするEthereumのコントラクトコードです。 これらは自分でデプロイしてそこに公開鍵やその他属性を追加してもよいですが、 ここ に各ネットワークにデプロイされているコントラクトがあるのでそれを利用しても問題ないです。(今回は事前にデプロイされているSepoliaネットワークのコントラクトを使用します) ethr-did-resolver 与えられたdidメソッド( did:ethr:development:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74 )をもとにDIDドキュメントを解決したり、VCを検証したりし、DIDドキュメントを返します。 ethr-did DIDの作成や更新をするために使用します。 実装するユースケース 大学の証明書を発行しそれを検証するようなものを想定します。 今回はKYC認証は行わないのでVCを提出しているユーザーがVCの本人かどうかは保証しません。あくまでVCが改ざんされていないかを検証するところのみになります。 実装手順 DIDの登録 大学が検証用の公開鍵をEthereum に登録します。 VCの発行 大学が証明書を発行 VCを検証 準備 Metamask Ethereumでトランザクションを発行するための秘密鍵と検証用公開鍵を取得する https://metamask.io/ja/ Alchemy Etheruemネットワークにアクセスするためにノードを提供してくれるサービス API Keyを取得する https://www.alchemy.com/ Sepoliaテストネットトークン ガス代の支払に使用するためのトークン https://cloud.google.com/application/web3/faucet/ethereum/sepolia https://www.infura.io/faucet/sepolia 実装 今回実装するソースコードは こちら で公開しています。 フロントエンド:React API:Nest.js どちらも簡単なコードで作りこみはしていません。 フロントエンドはフォームとレスポンスをわかりやすく作っているだけなので今回はAPIの app.service.ts を見ていきます。( app.controller.ts はルーティングしかしてませんす) 事前に紹介した三つの機能についてそれぞれ実装を紹介します。 DID登録 import { Injectable } from '@nestjs/common'; import { Resolver } from 'did-resolver'; import { getResolver } from 'ethr-did-resolver'; import { VerifiedCredential, verifyCredential } from 'did-jwt-vc'; import { EthrDID, KeyPair } from 'ethr-did'; import { ethers, Wallet } from 'ethers'; async registerDID(): Promise<void> { const alchemyApiKey = process.env.ALCHEMY_API_KEY!!; const privateKey = process.env.PRIVATE_KEY!!; const wallet = new Wallet(privateKey); const pubkey = wallet.signingKey.publicKey; const address = wallet.address; const provider = new ethers.AlchemyProvider('sepolia', alchemyApiKey); const txSigner = new Wallet(privateKey, provider); const keypair: KeyPair = { privateKey, publicKey: pubkey, address, identifier: pubkey, }; // ユーザーのDID作成 const ethrDid = new EthrDID({ ...keypair, provider: provider, txSigner: txSigner, // こいつを渡さないと失敗する <https://github.com/uport-project/ethr-did/issues/81#issuecomment-1030181286> chainNameOrId: 'sepolia', registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', }); // DID登録 await ethrDid.setAttribute( 'did/pub/Secp256k1/sigAuth/hex', pubkey, 31104000, ); } ここでは検証用に使用する公開鍵をEtheruemに登録する処理を行っています。 登録するスマートコントラクトはすでにライブラリ開発者がでプロしてくれているsepoliaのスマートコントラクトをレジストリとして使用させてもらいます。 0x03d5003bf0e79C5F5223588F347ebA39AfbC3818 Ethereumに登録するトランザクションはこれだけなので公開鍵の登録が終わったらガス代がこれ以降かかることはありません。 DID作成の時には公開鍵、秘密鍵、EOAアドレス、AlchemyProvider、Wallet、デプロイ先のチェーン、スマートコントラクトのアドレスを渡します。 const keypair: KeyPair = { privateKey, publicKey: pubkey, address, identifier: pubkey, }; // ユーザーのDID作成 const ethrDid = new EthrDID({ ...keypair, provider: provider, txSigner: txSigner, // こいつを渡さないと失敗する <https://github.com/uport-project/ethr-did/issues/81#issuecomment-1030181286> chainNameOrId: 'sepolia', registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', }); そして公開鍵の登録の際には公開鍵の形式と用途を指定して登録します。 登録方法は このように なっており、今回は以下のような意味を持っています・ DIDの公開鍵 であること。 Secp256k1 曲線に基づいた公開鍵暗号アルゴリズムを使用していること。 署名と認証 のために使用される公開鍵であること。 公開鍵の値は 16進数形式 で表現されていること。 // DID登録 await ethrDid.setAttribute( 'did/pub/Secp256k1/sigAuth/hex', pubkey, 31104000, ); VCの発行 async issueVc( hodlerAddress: string, type: string, name: string, ): Promise<string> { const vcpayload = { '@context': ['<https://www.w3.org/2018/credentials/v1>'], type: ['VerifiableCredential'], issuer: 'did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8', issuanceDate: new Date().toISOString(), credentialSubject: { id: `did:ethr:sepolia:${hodlerAddress}`, degree: { type: type, name: name, }, }, proof: { type: 'EcdsaSecp256k1Signature2019', created: new Date().toISOString(), proofPurpose: 'assertionMethod', verificationMethod: 'did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8#delegate-1', }, }; const alchemyApiKey = process.env.ALCHEMY_API_KEY!!; const privateKey = process.env.PRIVATE_KEY!!; const wallet = new Wallet(privateKey); const pubkey = wallet.signingKey.publicKey; const address = wallet.address; const provider = new ethers.AlchemyProvider('sepolia', alchemyApiKey); const txSigner = new Wallet(privateKey, provider); const keypair: KeyPair = { privateKey, publicKey: pubkey, address, identifier: address, }; // ユーザーのDID作成 const ethrDid = new EthrDID({ ...keypair, provider: provider, txSigner: txSigner, // こいつを渡さないと失敗する <https://github.com/uport-project/ethr-did/issues/81#issuecomment-1030181286> chainNameOrId: 'sepolia', registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', }); const vc = await ethrDid.signJWT(vcpayload); return vc; } VCにもW3Cが定めている規格が存在しています。 https://www.w3.org/TR/vc-data-model-2.0/ 今回は実装に焦点を当てるため詳細な説明は行いませんが、発行したいVCに合うような形でVCを作成する必要があります。 以下は今回使用するVCの簡単な紹介です。 @context VCが準拠する 標準的なデータ構造の文脈 を示します。 ['<https://www.w3.org/2018/credentials/v1>'] はW3Cが定めたVerifiable Credentialsの標準的な構造に従うことを示します。 type VCの種類を示すプロパティです。 ['VerifiableCredential'] は、この証明書が検証可能な証明書であることを明示しています。 issuer 証明書の発行者(Issuer)のDIDを示します。 この場合、発行者は EthereumのSepoliaテストネット 上にある did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8 というDIDを持つエンティティです。 issuanceDate この証明書が発行された 日付 を示します。 new Date().toISOString() を使って現在の日時をISO 8601形式で自動的に生成します。 credentialSubject 証明書が関連する 主体(Credential Subject)を定義します。このVCに記載される情報の対象 です。 id: did:ethr:sepolia:${hodlerAddress} では、証明書の対象となる人物や組織のDIDを示します。この部分は hodlerAddress という変数で動的に設定されるEthereumのアドレスです。 degree フィールドは証明書の内容を示しています。具体的には「学位」情報として、 type (学位の種類)と name (学位の名称)が含まれています。これらも変数 type と name によって動的に設定される情報です。 proof このVCがどのように 署名され、検証されるか を示す情報です。 type : EcdsaSecp256k1Signature2019 は、 Secp256k1 楕円曲線を使ったECDSA(楕円曲線デジタル署名アルゴリズム)による署名方式を指定しています。これはEthereumなどのブロックチェーンで広く使われている暗号方式です。 created : 署名が生成された日時です。ここでも new Date().toISOString() が使われ、現在の日時が設定されます。 proofPurpose : この署名が何のために行われるのかを示します。 assertionMethod は、発行者が証明書の内容を**主張(assert)**するために行った署名であることを意味します。 verificationMethod : 証明書の署名を 検証するための公開鍵 や検証方法を指定しています。この場合、 did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8#delegate-1 が指定されています。この公開鍵は #delegate-1 という識別子で特定されています。 またVCはJWTトークンとして返却されます。 const vc = await ethrDid.signJWT(vcpayload); 例えば以下のような内容でVCを作成しようとすると 以下のようなトークンが返ってきます。 これがVCになります。 eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE3Mjk1OTE2ODcsIkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmV0aHI6c2Vwb2xpYToweDE0NTI0MjI4NkFFODE4NGNBODg1RTZCMTM0RTFBMWJBNzM4NThCRTgiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTEwLTIyVDEwOjA4OjA3LjUwNFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDpldGhyOnNlcG9saWE6MHhFNDk1RkEwMmI3N0I3ZmVFMzVBMDQ2ODYwNDhEMDg0NzY1MTczMThFIiwiZGVncmVlIjp7InR5cGUiOiLmg4XloLHlt6XlraYiLCJuYW1lIjoi5bel5a2mIn19LCJwcm9vZiI6eyJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5IiwiY3JlYXRlZCI6IjIwMjQtMTAtMjJUMTA6MDg6MDcuNTA0WiIsInByb29mUHVycG9zZSI6ImFzc2VydGlvbk1ldGhvZCIsInZlcmlmaWNhdGlvbk1ldGhvZCI6ImRpZDpldGhyOnNlcG9saWE6MHgxNDUyNDIyODZBRTgxODRjQTg4NUU2QjEzNEUxQTFiQTczODU4QkU4I2RlbGVnYXRlLTEifSwiaXNzIjoiZGlkOmV0aHI6c2Vwb2xpYToweDE0NTI0MjI4NkFFODE4NGNBODg1RTZCMTM0RTFBMWJBNzM4NThCRTgifQ.nMBiW1Yi5vyghoorBRAwMIFHTRfATn_P3hv0ZF_aeeUowti7epUep90ldsuubkylccAmjlIiZwGcB9SgCd5IWAA VCの検証 最後に先ほど取得したVCを検証してみます。 async verifyVc(vc: string): Promise<VerifiedCredential | boolean> { const providerConfig = { // While experimenting, you can set a rpc endpoint to be used by the web3 provider rpcUrl: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`, // You can also set the address for your own ethr-did-registry (ERC1056) contract registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', name: 'sepolia', // this becomes did:ethr:development:0x... }; // It's recommended to use the multi-network configuration when using this in production // since that allows you to resolve on multiple public and private networks at the same time. // getResolver will return an object with a key/value pair of { "ethr": resolver } where resolver is a function used by the generic did resolver. const ethrDidResolver = getResolver(providerConfig); const didResolver = new Resolver(ethrDidResolver); try { const result = await verifyCredential(vc, didResolver); return result; } catch (error) { return false; } } DIDリゾルバーを使用して発行元のDIDドキュメントを取得し、ドキュメントから公開鍵を取得しユーザーから提供されたVCを検証します。 DIDリゾルバーはDIDメソッドごとに異なります。 DIDメソッドを管理している団体がDIDリゾルバを提供していることが多いようです。今回はERC1056で紹介されていたDIDリゾルバを使用します。 DIDの解決から検証までは以下のコードになります。 const ethrDidResolver = getResolver(providerConfig); const didResolver = new Resolver(ethrDidResolver); const result = await verifyCredential(vc, didResolver); 提供されているライブラリが優秀すぎて何をしているのかよくわからないので順に解説していきます。 まず最初の2行でDIDリゾルバーの設定を行います。 これはネットワークやリゾルバが参照するコントラクトのアドレスを設定します。 最後の1行でDIDを解決するところからVCの検証まで一括して行っています。 DIDの解決では先ほどのJWTトークンの中にあるissuerというプロパティのDIDを解決します。 参考までに先ほどのJWTトークンをデコードしてみます。 { "iat": 1729591687, "@context": [ "<https://www.w3.org/2018/credentials/v1>" ], "type": [ "VerifiableCredential" ], "issuer": "did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8", "issuanceDate": "2024-10-22T10:08:07.504Z", "credentialSubject": { "id": "did:ethr:sepolia:0xE495FA02b77B7feE35A04686048D08476517318E", "degree": { "type": "情報工学", "name": "工学" } }, "proof": { "type": "EcdsaSecp256k1Signature2019", "created": "2024-10-22T10:08:07.504Z", "proofPurpose": "assertionMethod", "verificationMethod": "did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8#delegate-1" }, "iss": "did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8" } これを見ると did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8 このDIDを解決していることがわかります。 続いてこのDIDを解決してみましょう。 DID解決を行ってくれる Universal Resolver というサイトがあります。 ここで先ほどのDIDを入力すると以下のような結果になります。 これを見ると以下の公開鍵で検証すればよいことがわかります。 今回検証に使うのは proof.verificationMethod に指定されているため 04a48ac40eade831fa352e56aaaab5396cf7f87627913543cf6d1dff5f5c0b496da62401ea4b0629fb3370d56855b2cf32d333e5c1bd0e53cf53722187d7eb3ff1 こちらの公開鍵を使用すればよいことがわかります。 "proof": { "type": "EcdsaSecp256k1Signature2019", "created": "2024-10-22T10:08:07.504Z", "proofPurpose": "assertionMethod", "verificationMethod": "did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8#delegate-1" }, 実際に今回のサンプルで検証してみるとvefified:trueと帰ってくるので検証に成功していることがわかります。 最後の検証の部分で渡しているものはVCのみで発行者の情報などはVCからすべて取得できているため発行者に検証に関する問い合わせは行っていないことがわかります。 終わりに 今回はEthereumを使用したDID/VCを実装してみました。 DID/VCを手軽に実装している記事が少ないため実装にはかなり苦労しましたが、動くものを見ると具体的なイメージがわきやすいですね。 DID/VCについてはまだまだ不勉強な部分も多く、デファクトスタンダードと呼ばれるものもないため今後の動向に注目しつつ、今回説明できなかったDID/VCの細かい部分も今後紹介できたらと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post EthereumでDID/VCを実装してみる first appeared on SIOS Tech. Lab .
bashの設定ファイルとは? 環境変数やエイリアス、関数等の定義はシェルが削除されるたびにその定義も一緒に削除されてしまいます。そこでそれらの定義をbash起動時に自動で行うための設定ファイルというものが用意されています。 表:bashの設定ファイル一覧 ※注意事項 /etcディレクトリ以下の設定ファイルは全ユーザーに作用するので、自分専用の設定を施したい場合はホームディレクトリ以下の設定ファイルにしましょう。 設定ファイルの実行順序 bashが起動された場合、以下のフロー図に従って設定ファイルが読み込まれることになります。 図:設定ファイルの実行順序 ※ 用語 ログインシェル:ユーザーがシステムにログインしたときに起動されるシェルです。ログインシェルは、ユーザーの認証後に起動する最初のシェルであり、ユーザーのプロファイルや環境設定を読み込みます。 対話型シェル:ユーザーがコマンドを直接入力して対話するために起動されるシェルです。ログインシェルではないが、コマンドラインで操作できるシェルを指します。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post bashの設定ファイルについて first appeared on SIOS Tech. Lab .