TECH PLAY

NTTドコモビジネス

NTTドコモビジネス の技術ブログ

602

DevOpsプラットフォームの取り組みを紹介する7回目の記事です。 Qmonus Value Stream 開発チームの奥井( @HirokiOkui )です。 連載第7回では、Qmonus Value Streamの中核を担うコンポーネントであるAssemblyLineについて深堀りします。 第2回 および 第6回 で解説したとおり、Qmonus Value Streamでは、AssemblyLineという独自のリソースを定義してCI/CDパイプラインを構成します。 AssemblyLineは、 Tekton と同様にKubernetesのカスタムオペレーターとして実装されています。 AssemblyLineは、Tekton Pipelineを実行するワークフローエンジンとしての責務に加えて、柔軟性の高いCI/CDパイプラインを構成・実行するために必要な様々な機能を有しています。 本記事では、AssemblyLineが有する機能の概要について紹介したのち、それらの機能をどのように実現しているかを解説します。 AssemblyLineが果たす3つの役割 Qmonus Value Streamでは、アプリケーションのビルド・デプロイ・リリースを一貫して行うCI/CDパイプラインをAssemblyLine(組立ライン)と呼称しています。 CI/CDパイプラインを構成する一連のリソースのうち一番上の層に位置するのがAssemblyLineであり、AssemblyLineを実行することでPipelineが駆動され、実際のタスク処理が実行されます。 AssemblyLineの構成例を以下に示します。 AssemblyLineは複数のステージから構成され、1つのステージは1つのPipelineで構成されます。 また、ステージ間の依存関係を定義することでDAG(有向非巡回グラフ)を形成し、各ステージを順番に実行できます。 このように、AssemblyLineに対して複数のPipelineを定義することで、多くの処理をまとめて実行する高度なCI/CDパイプラインを構成できます。 AssemblyLineは、TektonのPipelineやTaskと同様に、Kubernetesのカスタムリソースとして構成されています。 このため、AssemblyLineのマニフェストはKuberenetes YAMLの形式で記述します。 具体的な記述例は、 第2回 の記事を参照してください。 このように、Qmonus Value Streamでは、Pipeline/Taskとすでに2層構造(Stepも含めると3層構造)になっているTektonの構成に対して、さらにAssemblyLineという上位の層を追加しています。 複数のTaskを統合する層として既にPipelineが提供されている以上、この構成は冗長に思えることでしょう。 AssemblyLine層を追加した理由は、大きく分けて3つあります。 1. Pipeline・Taskを再利用可能な汎用モジュールにする AssemblyLine層が必要な1つ目の理由は、Pipeline・Taskの汎用性を高めて再利用可能にし、ユーザが自身で開発するCI/CDパイプラインマニフェストをできるだけ少なくするためです。 再利用可能な汎用モジュールとして実装しCloud Native Adapterに組み込むことで、ユーザはCloud Native Adapterを選択するだけで商用にデプロイするCI/CDパイプラインを構成することを可能にしています。 (Cloud Native Adapterについては、 第3回 をご覧ください) TektonのTaskとPipelineについて、簡単におさらいします。 Taskは、KubernetesのPodを生成し、TaskにStepとして定義された複数のコンテナを順番に起動する責務を持ちます。 KubernetesにおいてPodはワークロードリソースの最小の単位であり、これを起動する責務を持つことから、TaskはCI/CDパイプライン定義における最小の単位となります。 必要なコンテナを組み合わせることで、任意の処理を行うTaskが構成できます。 一方Pipelineは、自身に定義されたDAGに従ってTaskを順番に実行する責務を持ちます。 複数のTaskを直列・並列に組み合わせることで、任意の複雑なパイプラインを構成できます。 ここで、再利用性を高めるために汎化することを考えます。 Taskは複数コンテナを組み合わせて任意の処理を組み上げられますが、あまり多くのコンテナを包含して責務を持ちすぎると、汎用モジュールとして扱うことができません。 再利用性の観点から、1つもしくは数個のコンテナを使って、例えばGitからのチェックアウト、コンテナのビルド、スキャンなど、限られた責務だけを具備させることが望ましいです。 Taskを再利用可能な汎用モジュールとして構成すると、AssemblyLine層がない場合には、Taskを組み合わせてCI/CDパイプラインを構成することをPipeline一層だけで行わなければなりません。 単一のアプリケーションのビルド・デプロイ・テストだけを行うようなシンプルなCIであれば、1つのPipelineだけで十分でしょう。 一方で、総合検証環境でテスト用のカスタムビルドを作成しつつ、複数のマイクロサービスをデプロイするケースや、検証用に複数の試験環境を構成したい場合はどうでしょうか。 1つのPipelineで組み上げると複雑になりすぎるため、複数の異なるPipelineに分割し、それぞれを実行することになるでしょう。 複数のPipelineに分割された一方で実施したい目的は1つのため、複数のPipeline実行を自動化するためにシェルスクリプトなどでラップすることになりそうです。 Pipeline間に順序依存があったり、Pipelineの実行結果を後段のPipelineで利用する必要がある場合は、シェルスクリプトでワークフローもどきを書き始めることになってしまいます。 Qmonus Value Streamでは、Pipelineを分割し統合管理する必要が生じる複雑なユースケースを想定して、AssemblyLineという複数のPipelineを統合するコンポーネントを設けました。 そして、Pipelineの責務を「1つのアプリケーションを1つの環境に対して、ビルド・デプロイ・リリースなどのまとまった処理のみ実行する」ように限定することを選択しました。 この粒度で分割されたPipelineを組み合わせてAssemblyLineが構成され、AssemblyLineを実行することでCI/CDパイプラインを駆動します。 これにより、以下の効果を狙っています。 Pipelineが、ビルド・デプロイ・リリースというCI/CDパイプラインにおいて異なるアプリケーションや異なる環境向けに再利用しやすい粒度で構成されるため、これをビルディングブロックとして扱うことで、複数のアプリケーションや複数の環境を扱う複雑なCI/CDパイプラインでも簡単に組み上げることができます。 汎用Taskを実装する際に、単一責任の原則を守りやすくなります。 汎化の層がTaskだけの場合は、あまりに細かな粒度だとPipeline側で扱いにくくなることからTaskの粒度を小さくすることを妨げる圧力がかかります。 Pipelineも汎化の層となることで、Taskの再利用性を高めることがPipelineの構成の容易性を高めることに繋がり、Taskの汎化の価値が高まります。 AssemblyLineにより、複数Pipelineからなるワークフローを形成できるため、前述のように複数Pipelineの実行を自動化するためのスクリプティングを行う必要がなくなります。 Pipelineと同様の宣言的記述でAssemblyLineを表現し、宣言的なCI/CDパイプラインというエクスペリエンスを一貫します。 Cloud Native Adapterをコンパイルして生成されるPipelineは、上述のとおり「1つのアプリケーションを1つの環境に対して、ビルド・デプロイ・リリースなどのまとまった処理のみ実行する」責務のみを有しています。 下の図で示すように、生成されたPipelineをビルディングブロックとして扱い、リリースされるまでのライフサイクルを構成する一連のAssemblyLineにおいて複数回使用できます。 AssemblyLine、Pipeline、Taskのそれぞれの機能と責務をまとめると、以下のとおりです。 なお補足ですが、TektonにはPipeline-in-Pipelineという拡張機能があり、これを用いることで、Tekton公式が提供する機能だけでPipelineよりも上位の層を追加できます。 このため、本節に記載した1つ目の理由だけであれば、Pipeline-in-Pipelineを使用するという選択肢もあります。 2. パラメータマッピングと注入を集約する AssemblyLine層が必要な2つ目の理由は、Pipelineに対してパラメータを渡すためにパラメータをマッピングおよび注入する責務をAssemblyLineが担っているためです。 Qmonus Value Streamでは、複数の層でパラメータマッピングをするのではなく、パラメータが供給される層とパラメータを注入する層をすべてAssemblyLineに限定することで、パラメータ設定の透明性と保守性を高めています ( 第6回 で詳しく説明していますので、詳細は割愛します)。 具体例を下図に示します。AssemblyLineのステージ定義において、アプリケーションと環境のペアから構成されるスコープ(例:AppA x Staging)と、ビルド用の汎用Pipelineを指定することで、「AppA x Staging」用に配置されたパラメータセットをビルド用のPipelineのパラメータインターフェースに対して注入できます。 このパラメータマッピング・注入の機構により、大量のパラメータを小さなスコープに分割して効率的に管理できます。 3. リトライ機能の提供 Tektonを汎用のワークフローエンジンとして実用していくことを考えると、致命的に足りない機能があります。 まず、Pipeline/Taskを失敗したところからリトライする機能がありません。 一時的な事由により失敗したケースを考慮して、指定回数に限り自動でリトライする機能はありますが、手動で再開させることができません。 また、Taskが失敗したときに、DAGを逆方向にたどりながら補償トランザクション(実行前の状態に切り戻す処理)を実行していくこともできません。 これらの機能がないことはすなわち、ワークフローエンジンが一般的に備えている分散トランザクション管理機能が備わっていないことに相当します。 Tektonは、主たる責務がCIの実行であるというドメイン要件から、大胆に割り切った設計となっており、上記の機能不足は問題になりません。 CIではミッションクリティカルなシステムを扱わないため、仮に失敗しても再度実行して成功すれば良いと割り切ることができ、トランザクションの考慮を放棄できます。 重要なのは「再実行できる」ことだけであり、失敗時の未開放リソースの開放や再実行を妨げる中間ファイルの破棄さえできれば十分です。 このケースに対応するために、TektonはFinally機能を具備しており、Taskが失敗した場合に「再実行できる」状態まで戻すための術を利用者に提供しています。 「失敗してもPipelineの先頭から再実行できる」ことさえ担保すれば良い、と割り切って考えると、Tektonは必要な機能を効率的に具備していると言えます。 「失敗したらPipelineの先頭から再実行」と割り切るTektonの思想は理に適っているように見えますが、実シーンを考えると困るケースが多々あります。 例えば、デプロイの際にデータベースのマイグレーションを伴うケースはどうでしょうか。 クリティカルなエラーが出た場合には当然切り戻す必要がありますが、たまたまネットワークが瞬断していたとか、軽微な構成誤りであり即座に修正してデプロイを再開できるケースにおいて、データベースを切り戻して再度先頭からやり直すことは避けたいでしょう。 また、ビルドなど時間がかかる処理を伴う場合も、全体を再度やり直すことは避けたいでしょう(ビルドについては、アーティファクトの有無で分岐したりキャッシュを活用する方法もあります)。 このように、Task単位でのリトライが選択できずPipeline単位でのリトライしかできないのは、ユーザのオペレーションの選択肢を減らし、利便性を損なうことに繋がります。 利用者目線で見ると、失敗したTaskの先頭から前回実行時の状態を引き継いで再開できる方が、Pipelineの先頭からやり直しになるよりも自然です。 また、リトライ時に前回実行時のパイプライン定義・パラメータ・内部状態を引き継くことができません。 Qmonus Value StreamのAssemblyLineはCD機能も提供しており、1つのAssemblyLineで複数リージョンに対してデプロイをするようなユースケースを想定しています。 複数リージョンに対してデプロイしている最中に、途中で失敗したケースを考えてみます。 失敗した原因を解析し解消できた場合は、リトライにより失敗した環境のデプロイから再開しますが、初回実行とリトライ実行のパイプラインの構成やパラメータが変わってしまうと、複数のリージョンに対して一貫したデプロイを行うことができません。 リトライ実行であっても、前回実行時のパイプライン定義・パラメータ・内部状態を引き継ぎ、同じ状態で処理を再開することが必要となります。 このリトライ機能とリトライ時に状態を引き継ぐ機能は、CDツールには欠かせない機能となっています。 複数環境への決定的かつ一貫したデプロイを実現するために必須の機能であり、 Google Cloud Deploy などパブリッククラウドから提供されるCDツールにも類似の機能を具備されています。 Tektonでは具備されていないTask単位でのリトライと、リトライ時に状態を引き継ぐ機能を、AssemblyLineによって補います。 これが、AssemblyLineが必要となる3つ目の理由です。 なお、本節の冒頭で言及したTektonに補償トランザクションの機能がないという設計については、CI/CDパイプラインの取りうる状態と複雑性を減らす観点から、AssemblyLineでも同じ方針を採用しています。 AssemblyLineの実現方式 Kubernetes カスタムオペレーターとして実装 AssemblyLineは、Kubernetesのカスタムリソースとして定義されています。 Kubernetesは、ユーザが独自でカスタムリソースを定義することで様々な機能拡張ができます。 TektonのPipelineやTaskもKubernetesのカスタムリソースです。 Kubernetesでは、Podなどの標準のリソースもしくはカスタムリソースを宣言的に定義することで、制御対象のシステムをデプロイし、宣言された状態になるまで自動的にデプロイ処理が繰り返されます。 現在の状態(Current State)が宣言されたあるべき状態(Desired State)に至るまで繰り返されるループ処理のことをReconciliation Loopと呼び、Kubernetes上で定義されるあらゆるリソースは、Reconciliation Loopによって制御されます。 ( Managing Kubernetes より) Tekton Pipelineは、このReconciliation Loopの機構を使ってDAGで定義されたTaskを先頭から順番に実行するつくりになっており、AssemblyLineでも同様の仕組みを採用しています。 Tekton PipelineがTaskを実行する処理とAssemblyLineがPipelineを実行する処理は、ロジックがかなり似ています。 AssemblyLineの設計・実装においてTekton Pipelineの設計やアプローチを参考にすることで、Reconciliation Loopを用いてワークフローエンジンを実装するTektonのノウハウを活かしています。 カスタムリソースを用いてReconciliation Loopによる制御を行うには、Kubernetesの標準リソースとは異なり、Reconciliation Loopで実行される処理を実装する必要があります。 この処理を行うプログラムはカスタムコントローラと呼ばれ、カスタムリソースの定義マニフェスト(Custom Resource Definition: CRD)とカスタムコントローラの2つをあわせてオペレーターと呼びます。 オペレーターを実装するにはいくつかの方法がありますが、現在では kubebuilder を利用するのが一般的であり、AssemblyLineオペレーターもkubebuilderを用いて実装しました。 kubebuilderではカスタムリソースを定義・拡張する専用のCLIがあり、CRDやマニフェストの自動生成機能も充実しています。 Reconciliation Loopのロジック開発に集中し、簡単にオペレーターを開発できますので、興味があれば一度オペレーターの開発にチャレンジしてみてください。 AssemblyLineを構成する3つのカスタムリソース 以下の図に、AssemblyLineオペレーターを構成する3つのカスタムリソースを示します。 比較のために、Tekton Pipelineオペレーターを構成するカスタムリソースも併記しています。 第5回 でも紹介したとおり、Tektonは、Pipelineリソースで実行対象のTaskやパラメータマッピングを宣言的に定義し、PipelineRunリソースを定義することでワークフローを走らせます。 定義と実行で異なるリソースを用いるため、処理の内容をPipelineリソースに事前に定義しておき、実行時にはパラメータだけを指定してPipelineRunを作成する、という分担が可能です。 PipelineRunに都度実行対象のワークフロー全体を記述する手間が省けます。 PipelineRunはワークフローエンジンとして振る舞うためにカスタムコントローラが必要ですが、Pipelineは実行対象のTaskと実行順序が定義されているだけであり、カスタムコントローラがありません。 Pipelineで定義されているTaskは、PipelineRunを作成することで、カスタムコントローラによって実行されます。 AssemblyLineにおいても同様に、定義と実行でリソースを分離した構成を採用しています。 AssemblyLineに実行対象のPipeline・実行順序・パラメータマッピングを定義しますが、AssemblyLineは実際の処理を駆動しないため、カスタムコントローラがありません。 AssemblyLineで定義されているPipelineは、AssemblyLineFactoryを作成することで、カスタムコントローラによって実行されます。 AssemblyLineオペレーターでは、AssemblyLineFactoryとAssemblyLineRunの2つのカスタムリソースを用いてPipelineを実行します。 Tekton PipelineオペレーターではPipelineRunだけがこの責務を担っているため、大きく構成が異なっています。 これには、記事の前半で説明したAssemblyLineが必要となる理由のうち、3つめの「リトライ機能」が関係しています。 AssemblyLineオペレーターは、AssemblyLineFactoryでAssemblyLineRunの実行状態を管理しつつ前回実行時の状態を保持することで、「リトライ機能」を実現しています。 PipelineRunの責務が「1回のPipeline実行」である一方、AssemblyLineにはリトライ機能があることから、AssemblyLineRunの責務は「1回のAssemblyLine実行における1回のトライ」になります。 ただし、前述のとおり、リトライをしても同じデプロイ結果に収束するためには、前回のトライのパイプライン定義・パラメータ・内部状態を引き継ぐ必要があり、それらを永続化する機能が必要です。 その機能を担うのが、AssemblyLineFactoryです。 AssemblyLineFactoryは、「1回のAssemblyLine実行における複数回のトライ」を責務として持ち、過去のトライの状態を永続化し、リトライ実行時にはリトライ用に加工されたAssemblyLineRunを1つ生成します。 このため、N回リトライを行った場合は、AssemblyLineFactoryは1つですが、AssemblyLineRunはN+1このリソースが生成されることになります。 AssemblyLineオペレーターとTekton Pipelineオペレーターの違いをまとめると、以下の3つの点において設計に差異があります。 リトライを可能にするためにAssemblyLineFactoryが追加されている リトライ用のAssemblyLineRunを生成するために、初回実行時に使用したマニフェスト・パラメータ・進捗状況を管理する責務を担っている AssemblyLineRunがリトライの都度別のリソースとして生成される このようにAssemblyLineオペレーターは、Tekton Pipelineオペレーターの実装を参考にしつつも、Qmonus Value Streamにおいて要求されるAssemblyLineの3つの機能、特にリトライ機能の実現のために様々な追加拡張を導入したことで生み出されました。 KubernetesのReconciliation Loopをワークフローエンジンに応用するというTektonの発想はとても面白く、手続きアプローチで実装したワークフローエンジンに比べてエラーに対する頑強性が高いというメリットもあります。 しかしながら、いざAssemblyLineオペレーターを実装してみると、その時々のリソース状態に応じて次に実行する内容が変わるために条件分岐が増えてしまう問題、状態のパターン網羅が出来ておらず考慮外の状態に陥ってしまう問題、テストが難しい問題などにより、難易度の高い開発となりました。 これらの詳細については、また別の記事で改めて紹介できればと考えています。 おわりに DevOpsプラットフォームの取り組み連載の7回目の記事として、Qmonus Value Stream中核を担うコンポーネントであるAssemblyLineについて深堀りしました。 AssemblyLineには大きく3つの責務があること、Pipelineを参考にKubernetesのカスタムリソースとして実装されていることを紹介しました。 Qmonus Value Streamチームは、メンバ一人一人が、利用者であるアプリケーション開発者のために信念を持って活動を続けています。 プラットフォームを内製開発するポジション、プラットフォームを使ってNTTグループのクラウド基盤やCI/CDを構築支援するポジションそれぞれで 、一緒に働く仲間を募集していますので、興味を持たれた方は応募いただけると嬉しいです。 CI/CDプラットフォーム開発、ソフトウェアエンジニア NTTグループのクラウド基盤構築およびCI/CD導入、ソリューションアーキテクト 次回は、Cloud Native AdapterによるPipeline/Task生成機能について踏み込んでご紹介します。 お楽しみに!
アバター
サマリ SR-MPLS で構成されたネットワークにおいて、Per-Flow Steering を実現 IOS XR + Junos の Multi-vendor 環境で、5-tuple と IP Precedence のそれぞれを条件とした Traffic Engineering (TE) の検証に成功 この記事は Multi-AS Segment Routing 検証連載の第 8 回です。目次は こちら 概要 イノベーションセンターの三島です。本記事では Per-Flow での SR Policy 適用手法を紹介します。 SR Policy の適用については、これまでの連載記事にて宛先 prefix ごとの TE を実現する例を紹介しました。 より多様なユースケースを実現するためには prefix 単位の経路制御に加えて、アプリケーション単位や送信元単位など、様々な経路制御の実現が必要となります。 本記事ではそのような prefix 単位以外での SR Policy の適用方法を紹介します。 SR Policy の定義と適用方法 第 4 回記事 でも説明しました通り SR を用いた TE (SR-TE) の実現には SR Policy を利用します。 SR Policy は、2022 年 7 月に RFC9256 として発行されています。 この RFC では、SR Policy はどのノードへ送るかを指定する Endpoint、経路を指定する Segment List、属性である Color の 3 つの要素から構成されると定義されています。 今回のテーマである SR Policy の適用方法については、RFC9256 の 8 章にて下記が紹介されています。(一部本記事用に表現を編集) Local BSID Matching パケットへ付与された BSID に基づいて対応する SR Policy を適用する手法 Per-Destination Steering 宛先の経路に基づいて SR Policy を適用する手法 宛先の単位としては BGP で広告された prefix 単位、VRF 単位、広告元の Endpoint 単位が使用可能 SR Policy の自動インスタンス化が可能で、 第 7 回 で紹介した On-Demand Next-Hop(ODN) を使用可能 Per-Flow Steering パケットが持つ宛先以上の情報に基づいて SR Policy を適用する手法 ingress PE の ingress interface にて match 条件を評価、パケット識別のための Forwarding Class へ分類し、Class ごとに対応する SR Policy を選択 各レイヤーの条件としては次のような値が使用可能 L2 (Ethernet): destination/source/VLAN/CoS L3 (IP): destination/source/ToS (Differentiated Services Code Point (DSCP) / IP Precedence) L4 (transport): destination/source port Policy-Based Steering ルーティングポリシーに基づいて SR Policy を適用する手法 Policy-Based Routing の一種として実装 今回の記事では Per-Flow Steering の 実現例として、 5-tuple を基にした TE と IP Precedence を基にした TE をそれぞれ検証し紹介します。 検証 本章では、あるパケットを 5-tuple、IP Precedence のそれぞれで match させ、SR Policy を適用する例を検証します。 検証環境は下記の通りです。 事前準備 IOS XR では Per-Flow Steering を適用する BGP 経路に対して特定の Color をつける必要があります。 今回は、IBGP で対向の PE から受け取る全ての経路に color 1 を付与します。 extcommunity-set opaque COLOR-1 1 end-set route-policy COLOR-1-POLICY set extcommunity color COLOR-1 end-policy router bgp 65001 neighbor-group ibgp address-family vpnv4 unicast route-policy COLOR-1-POLICY in exit 5-tuple で match させる例 まずは、5-tuple で match したパケットのみ TE を適用する例を紹介します。 本章では以下の条件で match させ、SR Policy を適用していきます。 host1 -> host2: 送信先アドレスが 192.168.1.1 かつ、送信先ポートが 80 番 host2 -> host1: 送信先アドレスが 192.168.0.1 かつ、送信先ポートが 80 番 また、 match したパケットは下記の経路で転送されます。その他のパケットは IGP メトリックに従って転送されます。 パケットの分類 まず PE1、PE2 にて上記で指定した条件に一致するパケットを Forwarding Class に分類するための設定をします。 PE1(IOS XR) 5-tuple を定義した ACL を match 条件 とする class-map を定義します。 ipv4 access-list TCP-ANY-ANY-192.168.1.1-80 1 permit tcp any host 192.168.1.1 eq 80 ! class-map type traffic match-all 5TUPLE-CLASSMAP match access-group ipv4 TCP-ANY-ANY-192.168.1.1-80 end-class-map class-map と forward-class を対応させる policy-map を定義し、customer 向けの interface に attach します。 interface TenGigE0/0/0/33.100 service-policy type pbr input 5TUPLE-POLICYMAP ! policy-map type pbr 5TUPLE-POLICYMAP class type traffic 5TUPLE-CLASSMAP set forward-class 1 ! class type traffic class-default ! end-policy-map 作成した class-map と policy-map を確認します。 RP/0/RSP0/CPU0:PE1#show class-map type traffic 5TUPLE-CLASSMAP Thu Sep 8 13:33:34.767 JST 1) ClassMap: 5TUPLE-CLASSMAP Type: traffic Referenced by 1 Policymaps RP/0/RSP0/CPU0:PE1#show policy-map type pbr pmap-name 5TUPLE-POLICYMAP detail Thu Sep 8 14:24:51.531 JST ipv4 access-list TCP-ANY-ANY-192.168.1.1-80 1 permit tcp any host 192.168.1.1 eq www class-map type traffic match-all 5TUPLE-CLASSMAP match access-group ipv4 TCP-ANY-ANY-192.168.1.1-80 end-class-map ! policy-map type pbr 5TUPLE-POLICYMAP class type traffic 5TUPLE-CLASSMAP set forward-class 1 ! class type traffic class-default ! end-policy-map ! PE2(Junos) Multifield Classifier の firewall filter を設定します。 set firewall family inet filter 5TUPLE term TCP-ANY-ANY-192.168.0.1-80 from destination-address 192.168.0.1/32 set firewall family inet filter 5TUPLE term TCP-ANY-ANY-192.168.0.1-80 from protocol tcp set firewall family inet filter 5TUPLE term TCP-ANY-ANY-192.168.0.1-80 then count 5TUPLE-COUNT set firewall family inet filter 5TUPLE term TCP-ANY-ANY-192.168.0.1-80 then forwarding-class assured-forwarding set firewall family inet filter 5TUPLE term TCP-ANY-ANY-192.168.0.1-80 then accept set firewall family inet filter 5TUPLE term DEFAULT from protocol-except tcp set firewall family inet filter 5TUPLE term DEFAULT then count DEFAULT-COUNT set firewall family inet filter 5TUPLE term DEFAULT then forwarding-class best-effort set firewall family inet filter 5TUPLE term DEFAULT then accept set interfaces xe-0/1/7 unit 100 family inet filter input 5TUPLE 作成した firewall filter を確認します。 user@PE2> show firewall filter 5TUPLE Filter: 5TUPLE Counters: Name Bytes Packets 5TUPLE-COUNT 0 0 DEFAULT-COUNT 0 0 SR Policy の定義 作成した Forwarding Class に対応する SR Policy を定義します。 PE1(IOS XR) Explicit-Path と SR Policy を定義します。 5-tuple 条件と一致した場合の TE パスに加え、デフォルトの TE パスも定義する必要があります。 IOS XR においては、Forwarding Class ごとに分類されたパケットへの TE の適用は policy 5TUPLE-MATCH のように SR Policy を用いて行います。 segment-routing traffic-eng segment-list 5TUPLE-TE index 1 mpls label 16003 index 2 mpls label 16004 index 3 mpls label 16002 ! policy FC-1 color 101 end-point ipv4 10.255.1.2 candidate-paths preference 100 explicit segment-list 5TUPLE-TE ! ! ! ! policy DEFAULT color 100 end-point ipv4 10.255.1.2 candidate-paths preference 100 dynamic metric type igp ! ! ! ! ! policy 5TUPLE-MATCH color 1 end-point ipv4 10.255.1.2 candidate-paths preference 100 per-flow forward-class 0 color 100 forward-class 1 color 101 forward-class default 0 作成した SR Policy を確認します。 RP/0/RSP0/CPU0:PE1# show segment-routing traffic-eng policy Thu Sep 8 14:27:40.143 JST SR-TE policy database --------------------- Color: 101, End-point: 10.255.1.2 Name: srte_c_101_ep_10.255.1.2 Status: Admin: up Operational: up for 3d02h (since Sep 5 18:15:54.714) Candidate-paths: Preference: 100 (configuration) (active) Name: FC-1 Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Explicit: segment-list 5TUPLE-TE (valid) Weight: 1, Metric Type: TE 16003 [Prefix-SID, 10.255.1.5] 16004 16002 Attributes: Binding SID: 24023 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Max Install Standby Candidate Paths: 0 Color: 100, End-point: 10.255.1.2 Name: srte_c_100_ep_10.255.1.2 Status: Admin: up Operational: up for 3d01h (since Sep 5 18:49:30.081) Candidate-paths: Preference: 100 (configuration) (active) Name: DEFAULT Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) Metric Type: IGP, Path Accumulated Metric: 10 16002 [Prefix-SID, 10.255.1.2] Attributes: Binding SID: 24025 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Max Install Standby Candidate Paths: 0 Color: 1, End-point: 10.255.1.2 Name: srte_c_1_ep_10.255.1.2 Status: Admin: up Operational: up for 00:10:21 (since Sep 8 20:17:19.421) Candidate-paths: Preference: 100 (configuration) (active) Name: 5TUPLE-MATCH Requested BSID: dynamic Per-flow Information: Forward PDP PDP Class Color Status ------- ------- ------- 0 100 Up 1 101 Up Default Forward Class: 0 Attributes: Binding SID: 24006 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Max Install Standby Candidate Paths: 0 PE2(Junos) Explicit-Path と SR Policy を定義します。 5-tuple 条件と一致した場合の TE パスに加え、デフォルトの TE パスも定義する必要があります。 Junos においては、Forwarding Class ごとに分類されたパケットへの TE の適用は 5TUPLE-FORWARDING などのように routing-options を用いて行います。 set protocols source-packet-routing segment-list 5TUPLE-TE P2 label 16004 set protocols source-packet-routing segment-list 5TUPLE-TE P1 label 16003 set protocols source-packet-routing segment-list 5TUPLE-TE PE1 label 16001 set protocols source-packet-routing source-routing-path FC-1 to 10.255.1.1 set protocols source-packet-routing source-routing-path FC-1 primary 5TUPLE-TE set protocols source-packet-routing compute-profile DYNAMIC-IGP metric-type igp set protocols source-packet-routing source-routing-path DEFAULT to 10.255.1.1 set protocols source-packet-routing source-routing-path DEFAULT primary odn_path compute DYNAMIC-IGP set class-of-service forwarding-policy next-hop-map 5TUPLE-POLICYMAP forwarding-class best-effort lsp-next-hop DEFAULT set class-of-service forwarding-policy next-hop-map 5TUPLE-POLICYMAP forwarding-class assured-forwarding lsp-next-hop FC-1 set policy-options policy-statement 5TUPLE-FORWARDING then cos-next-hop-map 5TUPLE-POLICYMAP set routing-options forwarding-table export 5TUPLE-FORWARDING 作成した SR Policy を確認します。 user@PE2> show spring-traffic-engineering lsp detail Name: DEFAULT Tunnel-source: Static configuration Tunnel Forward Type: SRMPLS To: 10.255.1.1 State: Up Path: odn_path Path Status: NA Outgoing interface: NA Auto-translate status: Disabled Auto-translate result: N/A Compute Status:Enabled , Compute Result:success , Compute-Profile Name:DYNAMIC-IGP Total number of computed paths: 1 Computed-path-index: 1 BFD status: N/A BFD name: N/A TE metric: 10, IGP metric: 10 Delay metrics: Min: 16777215, Max: 16777215, Avg: 16777215 Metric optimized by type: IGP computed segments count: 1 computed segment : 1 (computed-node-segment): node segment label: 16001 router-id: 10.255.1.1 Name: FC-1 Tunnel-source: Static configuration Tunnel Forward Type: SRMPLS To: 10.255.1.1 State: Up Path: 5TUPLE-TE Path Status: NA Outgoing interface: NA Auto-translate status: Disabled Auto-translate result: N/A Compute Status:Disabled , Compute Result:N/A , Compute-Profile Name:N/A BFD status: N/A BFD name: N/A Segment ID : 128 ERO Valid: true SR-ERO hop count: 3 Hop 1 (Strict): NAI: None SID type: 20-bit label, Value: 16004 Hop 2 (Strict): NAI: None SID type: 20-bit label, Value: 16003 Hop 3 (Strict): NAI: None SID type: 20-bit label, Value: 16001 疎通確認 mtr を用いて経路を確認します。 host1 user@host1:~$ mtr -n -e 192.168.1.1 host1 (192.168.0.1) 2022-09-08T23:33:23+0000 Keys: Help Display mode Restart statistics Order of fields quit Packets Pings Host Loss% Snt Last Avg Best Wrst StDev 1. 192.168.0.254 0.0% 5 1.5 1.4 1.3 1.5 0.1 2. 192.168.1.254 0.0% 4 0.3 0.4 0.3 0.5 0.1 3. 192.168.1.1 0.0% 4 0.6 0.5 0.4 0.6 0.1 user@host1:~$ mtr -n -e -T -P 80 192.168.1.1 host1 (192.168.0.1) 2022-09-08T23:30:58+0000 Keys: Help Display mode Restart statistics Order of fields quit Packets Pings Host Loss% Snt Last Avg Best Wrst StDev 1. 192.168.0.254 0.0% 5 1.5 1.5 1.3 1.6 0.1 2. 10.1.1.2 0.0% 5 1.9 2.8 1.9 3.5 0.6 [MPLS: Lbl 16004 TC 0 S 0 TTL 1] [MPLS: Lbl 16002 TC 0 S 0 TTL 1] [MPLS: Lbl 123 TC 0 S 1 TTL 1] 3. (waiting for reply) 4. 192.168.1.254 0.0% 5 0.7 2.3 0.7 8.6 3.5 5. 192.168.1.1 0.0% 5 0.5 0.5 0.4 0.6 0.1 host2 user@host2:~$ mtr -n 192.168.0.1 host2 (192.168.1.1) 2022-09-08T23:36:14+0000 Keys: Help Display mode Restart statistics Order of fields quit Packets Pings Host Loss% Snt Last Avg Best Wrst StDev 1. 192.168.1.254 0.0% 5 0.5 0.5 0.3 0.6 0.1 2. 10.1.2.1 0.0% 5 1.4 1.5 1.4 1.6 0.1 3. 192.168.0.1 0.0% 5 0.4 0.5 0.4 0.5 0.0 user@host2:~$ mtr -n -T -P 80 192.168.0.1 host2 (192.168.1.1) 2022-09-08T23:35:22+0000 Keys: Help Display mode Restart statistics Order of fields quit Packets Pings Host Loss% Snt Last Avg Best Wrst StDev 1. 192.168.1.254 0.0% 7 0.6 1.9 0.3 10.0 3.6 2. (waiting for reply) 3. 10.1.4.1 0.0% 7 3.1 3.1 2.0 3.9 0.6 [MPLS: Lbl 16001 TC 0 S 0 TTL 1] [MPLS: Lbl 24002 TC 0 S 1 TTL 2] 4. 10.1.1.1 0.0% 7 1.5 1.5 1.3 1.6 0.1 5. 192.168.0.1 0.0% 7 0.4 0.5 0.4 0.5 0.1 IP Precedence で match させる例 IP Precedence で match したパケットのみ TE を適用する例を紹介します。 本章では host1 と host2 間にていずれの方向にも IP Precedence == 1 で match させ、SR Policy を適用していきます。 match したパケットは下記の経路で転送されます。その他のパケットは IGP メトリックに従って転送されます。 なお、一部設定は前章での 5-tupple による TE の設定と競合するため、検証をなぞる際は別環境で行うか前章での設定を削除してから行ってください。 パケットの分類 まず PE1、PE2 にて上記で指定した条件に一致するパケットを Forwarding Class に分類するための設定をします。 PE1(IOS XR) IP Precedence を match 条件 とする class-map を定義します。 class-map type traffic match-all IP-PRECEDENCE1 match precedence 1 end-class-map class-map と forward-class を対応させる policy-map を定義し、customer 向けの interface に attach します。 policy-map type pbr PRECEDENCE1-POLICYMAP class type traffic IP-PRECEDENCE1 set forward-class 1 ! class type traffic class-default ! end-policy-map interface TenGigE0/0/0/33.100 service-policy type pbr input PRECEDENCE1-POLICYMAP 作成した class-map と policy-map を確認します。 RP/0/RSP0/CPU0:PE1#show class-map type traffic IP-PRECEDENCE1 Thu Sep 8 14:31:23.599 JST 1) ClassMap: IP-PRECEDENCE1 Type: traffic Referenced by 0 Policymaps RP/0/RSP0/CPU0:PE1#show policy-map type pbr pmap-name PRECEDENCE1-POLICYMAP detail Thu Sep 8 14:41:12.876 JST class-map type traffic match-all IP-PRECEDENCE1 match precedence 1 end-class-map ! policy-map type pbr PRECEDENCE1-POLICYMAP class type traffic IP-PRECEDENCE1 set forward-class 1 ! class type traffic class-default ! end-policy-map ! PE2(Junos) Multifield Classifier の firewall filter を設定します。 set firewall family inet filter IP-PRECEDENCE1 term 1 from precedence 1 set firewall family inet filter IP-PRECEDENCE1 term 1 then count IP-PRECEDENCE1-COUNT set firewall family inet filter IP-PRECEDENCE1 term 1 then forwarding-class assured-forwarding set firewall family inet filter IP-PRECEDENCE1 term 1 then accept set firewall family inet filter IP-PRECEDENCE1 term 2 from precedence-except 1 set firewall family inet filter IP-PRECEDENCE1 term 2 then count NOT-IP-PRECEDENCE1 set firewall family inet filter IP-PRECEDENCE1 term 2 then forwarding-class best-effort set firewall family inet filter IP-PRECEDENCE1 term 2 then accept set interfaces xe-0/1/7 unit 100 family inet filter input IP-PRECEDENCE1 作成した firewall filter を確認します。 user@PE2> show firewall filter IP-PRECEDENCE1 Filter: IP-PRECEDENCE1 Counters: Name Bytes Packets IP-PRECEDENCE1-COUNT 0 0 NOT-IP-PRECEDENCE1 0 0 SR Policy の定義 作成した Forwarding Class に対応する SR Policy を定義します。 PE1(IOS XR) Explicit-Path と SR Policy を定義します。 segment-routing traffic-eng segment-list IP-PRECEDENCE-TE index 2 mpls label 16003 index 3 mpls label 16002 ! policy FC-1 color 101 end-point ipv4 10.255.1.2 candidate-paths preference 100 explicit segment-list IP-PRECEDENCE-TE ! ! ! ! policy DEFAULT color 100 end-point ipv4 10.255.1.2 candidate-paths preference 100 dynamic metric type igp ! ! ! ! ! policy IP-PRECEDENCE-MATCH color 1 end-point ipv4 10.255.1.2 candidate-paths preference 100 per-flow forward-class 0 color 100 forward-class 1 color 101 forward-class default 0 作成した SR Policy を確認します。 RP/0/RSP0/CPU0:PE1#show segment-routing traffic-eng policy detail Fri Sep 9 08:44:11.638 JST SR-TE policy database --------------------- Color: 101, End-point: 10.255.1.2 Name: srte_c_101_ep_10.255.1.2 Status: Admin: up Operational: up for 3d14h (since Sep 5 18:15:54.714) Candidate-paths: Preference: 100 (configuration) (active) (reoptimizing) Name: FC-1 Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Explicit: segment-list IP-PRECEDENCE-TE (valid) Weight: 1, Metric Type: TE 16003 [Prefix-SID, 10.255.1.5] 16002 LSPs: LSP[0]: LSP-ID: 2 policy ID: 2 (active) Local label: 24022 State: Programmed Binding SID: 24023 LSP[1]: LSP-ID: 3 policy ID: 2 (reoptimized) Local label: 24004 State: Install timer pending Attributes: Binding SID: 24023 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Max Install Standby Candidate Paths: 0 Color: 100, End-point: 10.255.1.2 Name: srte_c_100_ep_10.255.1.2 Status: Admin: up Operational: up for 3d13h (since Sep 5 18:49:30.081) Candidate-paths: Preference: 100 (configuration) (active) Name: DEFAULT Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) Metric Type: IGP, Path Accumulated Metric: 10 16002 [Prefix-SID, 10.255.1.2] LSPs: LSP[0]: LSP-ID: 2 policy ID: 4 (active) Local label: 24024 State: Programmed Binding SID: 24025 Attributes: Binding SID: 24025 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Max Install Standby Candidate Paths: 0 Color: 1, End-point: 10.255.1.2 Name: srte_c_1_ep_10.255.1.2 Status: Admin: up Operational: up for 00:00:03 (since Sep 9 08:44:08.469) Candidate-paths: Preference: 100 (configuration) (active) Name: IP-PRECEDENCE-MATCH Requested BSID: dynamic Per-flow Information: Forward PDP PDP Class Color Status ------- ------- ------- 0 100 Up 1 101 Up Default Forward Class: 0 LSPs: LSP[0]: LSP-ID: 4 policy ID: 5 (active) State: Programmed Binding SID: 24014 Attributes: Binding SID: 24014 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Max Install Standby Candidate Paths: 0 PE2(Junos) Explicit-Path と SR Policy を定義します。 set protocols source-packet-routing segment-list IP-PRECEDENCE-TE P1 label 16003 set protocols source-packet-routing segment-list IP-PRECEDENCE-TE PE1 label 16001 set protocols source-packet-routing source-routing-path FC-1 to 10.255.1.1 set protocols source-packet-routing source-routing-path FC-1 primary IP-PRECEDENCE-TE set protocols source-packet-routing compute-profile DYNAMIC-IGP metric-type igp set protocols source-packet-routing source-routing-path DEFAULT to 10.255.1.1 set protocols source-packet-routing source-routing-path DEFAULT primary odn_path compute DYNAMIC-IGP set class-of-service forwarding-policy next-hop-map IP-PRECEDENCE-POLICYMAP forwarding-class best-effort lsp-next-hop DEFAULT set class-of-service forwarding-policy next-hop-map IP-PRECEDENCE-POLICYMAP forwarding-class assured-forwarding lsp-next-hop FC-1 set policy-options policy-statement IP-PRECEDENCE-FORWARDING then cos-next-hop-map IP-PRECEDENCE-POLICYMAP set routing-options forwarding-table export IP-PRECEDENCE-FORWARDING 作成した SR Policy を確認します。 user@PE2> show spring-traffic-engineering lsp detail Name: DEFAULT Tunnel-source: Static configuration Tunnel Forward Type: SRMPLS To: 10.255.1.1 State: Up Path: odn_path Path Status: NA Outgoing interface: NA Auto-translate status: Disabled Auto-translate result: N/A Compute Status:Enabled , Compute Result:success , Compute-Profile Name:DYNAMIC-IGP Total number of computed paths: 1 Computed-path-index: 1 BFD status: N/A BFD name: N/A TE metric: 10, IGP metric: 10 Delay metrics: Min: 16777215, Max: 16777215, Avg: 16777215 Metric optimized by type: IGP computed segments count: 1 computed segment : 1 (computed-node-segment): node segment label: 16001 router-id: 10.255.1.1 Name: FC-1 Tunnel-source: Static configuration Tunnel Forward Type: SRMPLS To: 10.255.1.1 State: Up Path: IP-PRECEDENCE-TE Path Status: NA Outgoing interface: NA Auto-translate status: Disabled Auto-translate result: N/A Compute Status:Disabled , Compute Result:N/A , Compute-Profile Name:N/A BFD status: N/A BFD name: N/A Segment ID : 128 ERO Valid: true SR-ERO hop count: 2 Hop 1 (Strict): NAI: None SID type: 20-bit label, Value: 16003 Hop 2 (Strict): NAI: None SID type: 20-bit label, Value: 16001 疎通確認 mtr を用いて経路を確認します。 host1 通常の ICMP パケットは最短経路を通ります。 user@host1:~$ mtr -n -e 192.168.1.1 host1 (192.168.0.1) 2022-09-08T23:56:49+0000 Keys: Help Display mode Restart statistics Order of fields quit Packets Pings Host Loss% Snt Last Avg Best Wrst StDev 1. 192.168.0.254 0.0% 6 1.4 1.4 1.3 1.5 0.1 2. 192.168.1.254 0.0% 5 0.5 0.5 0.3 0.6 0.1 3. 192.168.1.1 0.0% 5 0.4 0.4 0.4 0.5 0.0 IP Precedence が 1(ToS が 32)のパケットは想定通りに TE が適用されています。 user@host1:~$ mtr -n -e -Q 32 192.168.1.1 host1 (192.168.0.1) 2022-09-09T00:03:10+0000 Keys: Help Display mode Restart statistics Order of fields quit Packets Pings Host Loss% Snt Last Avg Best Wrst StDev 1. 192.168.0.254 0.0% 5 1.5 1.5 1.4 1.6 0.1 2. 10.1.1.2 0.0% 4 2.7 2.8 2.0 3.7 0.7 [MPLS: Lbl 16002 TC 1 S 0 TTL 1] [MPLS: Lbl 123 TC 1 S 1 TTL 1] 3. 192.168.1.254 0.0% 4 0.4 0.5 0.4 0.6 0.1 4. 192.168.1.1 0.0% 4 0.5 0.5 0.4 0.5 0.0 host2 通常の ICMP パケットは最短経路を通ります。 user@host2:~$ mtr -n 192.168.0.1 host2 (192.168.1.1) 2022-09-09T00:03:41+0000 Keys: Help Display mode Restart statistics Order of fields quit Packets Pings Host Loss% Snt Last Avg Best Wrst StDev 1. 192.168.1.254 0.0% 6 10.8 8.0 0.4 13.7 6.0 2. 10.1.2.1 0.0% 5 1.6 1.6 1.4 1.7 0.1 3. 192.168.0.1 0.0% 5 0.4 0.5 0.4 0.5 0.0 IP Precedence が 1(ToS が 32)のパケットは想定通りに TE が適用されています。 user@host2:~$ mtr -n -T -P 80 192.168.0.1 host2 (192.168.1.1) 2022-09-09T00:04:23+0000 Keys: Help Display mode Restart statistics Order of fields quit Packets Pings Host Loss% Snt Last Avg Best Wrst StDev 1. 192.168.1.254 0.0% 9 0.5 0.4 0.4 0.5 0.1 2. 10.1.3.2 0.0% 9 2.8 2.9 1.9 3.5 0.5 [MPLS: Lbl 16001 TC 1 S 0 TTL 1] [MPLS: Lbl 24002 TC 1 S 1 TTL 1] 3. 10.1.1.1 0.0% 8 1.5 1.5 1.4 1.6 0.1 4. 192.168.0.1 0.0% 8 0.5 0.5 0.5 0.6 0.0 まとめ 本記事では、SR Policy の適用手法をまとめることで宛先 prefix よりも細かい単位での経路制御を紹介しました。 検証では例として 5-tuple と IP Precedence のそれぞれを match 条件として実装しましたが、実運用ではユースケースに応じて適切な match 条件を選択してください。 次回の記事では TI-LFA を利用した 高速迂回(FastReRoute)について紹介予定です。 (2022/10/03 追記) 公開しました: [Multi-AS Segment Routing 検証連載 #9] TI-LFA を用いた障害時の高速迂回
アバター
はじめに イノベーションセンター Network Analytics for Security (以下、NA4Sec) プロジェクトリーダーの神田です。 NA4Secチームでは兄弟プロジェクトである Metemcyber ( @Metemcyber ) *1 と連携して、サイバー脅威インテリジェンス(以下、脅威インテリジェンス) *2 の配信をはじめました。 本記事ではこの取り組みについて、発案者であるチームメンバーの @strinsert1Na (以下、strinsert1Na) との対談も交えながら紹介します。 NA4Secプロジェクトとは? NA4Secプロジェクトは「NTTはインターネットを安心、安全にする社会的責務がある」を理念として、攻撃インフラの解明、撲滅を目指すプロジェクトです。 NTT Com イノベーションセンターのみならずNTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズ(以下、NFLabs. )からもメンバーが参画し、日夜攻撃インフラを追跡しています。 また、NTT Comグループにおける脅威インテリジェンスチームとしての側面も持ち合わせており、有事において脅威インテリジェンスを提供し、意思決定を支援することもあります。 一例としてアクセスログ解析サービス Visionalist で使用していたドメインが第三者によりドロップキャッチされた際には、不審なスクリプトが配置されたインフラを分析し、リスクを評価するとともに対応を提言しました。 *3 脅威インテリジェンス配信はじめました 今年度に入ってNFLabs. からマルウェアの静的解析を得意とする strinsert1Na がチームに参画しました。 ここからは脅威インテリジェンス配信をはじめた経緯やその狙いについて発案者 strinsert1Na との対談形式で紹介します。 経緯 神田(以下、kanda): 今年度からNA4Secチームに参画してもらったわけですが、もともと脅威インテリジェンス発信には興味があったのですか? strinsert1Na: もともと個人の趣味で標的型攻撃の検体を収集したり解析したりしていて、情報発信もしていました。 しかし、マルウェア解析はすごくリソースが必要な行為だし多くの企業ができるものでもない。脅威インテリジェンスサービスもすぐにIoC(Indicator of Compromise) *4 を公開するとは限らない。 公開情報から得られたマルウェアのIoCを信頼できる場所を使って発信できないだろうか、と考えていました。 kanda: そんなときに隣のMetemcyberチームがTwitterアカウントを使って発信する情報を探していることを知って、これはちょうどいいぞ、と。 strinsert1Na: はい(笑)。脅威インテリジェンス関連を扱っているプロジェクトだし、「そのまま脅威インテリジェンスを発信させていただきますかー」程度のノリでやってみることにしました。 狙い kanda: 脅威インテリジェンスを発信するにあたっての思いや狙いについて聞かせてください。 strinsert1Na: 私は「日本に関連する標的型攻撃を継続的にウォッチするアカウントが欲しいなぁ」と前々から思っていたので、この理想を自分で作ってみようかなと思いました。 標的型攻撃は時々刻々と手口が変化するため、「一度レポートを読んで終わり」ではなく継続的な観測と対策が必要不可欠だと私は考えています。しかし、これらの関連情報やIoCは初報以降、継続的に公開されることは多くなく、海外の脅威インテリジェンスサービスが日本関連の脅威インテリジェンスをリアルタイムに生成することも少ないです。 なので、本Twitterアカウントを監視するだけでなるべく多くの人が標的型攻撃のIoCをリアルタイムで観測し、具体的な防御に活かせるようになれば...というのが狙いです。 kanda: ないなら自らやってやろうというわけですね。情報発信するにあたって何か意識したことはありますか? strinsert1Na: 一般人が流し読むような”ポエム”ではなく、現場のエンジニアがインシデントハンドリング/スレットハンティング *5 で活用できるような”戦術(Tactical)脅威インテリジェンス”の側面から価値を提供することを一番意識しています。 なので、マルウェア/インテリジェンスアナリストがTweetを見ただけで脅威インテリジェンスの真偽を確認できて、IoCを収集できるような投稿にすることを心がけました。 本Twitterアカウントの情報を自動収集することで、即座に防御に対する意思決定に貢献できれば嬉しいなと思っています。 実際に情報発信してみて kanda: 実際に発信してみてどうでした? strinsert1Na: 「脅威インテリジェンス発信と言ってもTwitterに投稿するだけでしょ?」と軽く考えていましたが、使いやすいフォーマットやぱっと見でのわかりやすさ、140字でのまとめ方など創意工夫がかなり必要でした。 SHA256ハッシュを載せたいけど載せるとハッシュ値とC2の情報だけで140文字消化してしまうし、IoCとして有効かをきちんと判断できる証拠画像も欲しいし。 結果として絵文字を使ってビジュアルに表現しながら文字をなるべく削り、IoCベースですぐに脅威インテリジェンスとして消費できる形にまとめました。 自分の人生で最も1Tweetに時間がかかりましたが、納得のできる形になったのではないかと思っています。 🚨⚡ #LODEINFO (🇯🇵) C2: 🌍 http[:]//172.104.72[.]4/ (ASN: AS63949, ISP: Linode, LLC 🇯🇵) and 2 more (172.105.223[.]216, 45.77.28[.]124). Family: 🦠 LODEINFO (v0.6.2) MD5: 🔒 da1c9006b493d7e95db4d354c5f0e99f Sample: 📄 https://t.co/MvDcBjsfwV H/T to NA4Sec Team pic.twitter.com/YUhX1WCrWH — Metemcyber (@Metemcyber) 2022年8月5日 kanda: 情報発信後の反響や手応えは? strinsert1Na: 発信した情報をプロに拾ってもらってより詳細なアーティファクト分析 *6 の提供につながった事例が早速ありました。まだ2Tweetしかしていませんが、再現性のある脅威インテリジェンスはきちんと世の中の役に立っているのだということが実感できました。 Nice share! 🦠 HUI Loader (d839ab2c2ea8f082f98478cb552d6709) ☠️Create a service name "WxUpdateServiceInfo" ->spawn thread for load system.ini-> decoding sc and inject decoded sc into svchost.exe (1/4) https://t.co/zs3buBPm3e pic.twitter.com/yrJx99ePpF — m4n0w4r (@kienbigmummy) 2022年8月25日 今後の意気込み kanda: 最後に今後の意気込みをぜひ。 strinsert1Na: これからも公開情報をもとに継続的に観測を続け、IoCを発信していくつもりです。 JPCERT/CCやベンダのレポートからハンティングに使うルールを日々アップデートしながら、今後も最新の情報を追えるように努力していきます。 また、Twittterに載せる脅威インテリジェンスはなるべく自動収集できる形にできればいいなと考えていて、今後も決まったフォーマットで発信するつもりです。 みなさんの意見を取り入れつつ、改善してより使いやすい形にしていきたいと思っているので、公式アカウント( @Metemcyber )や私のアカウント( @strinsert1Na )にReplyでフィードバックをいただけると助かります。 おわりに 本記事では、NA4SecチームとMetemcyberチームで連携した脅威インテリジェンス発信について紹介しました。 今後も様々な脅威インテリジェンスを発信していく予定です。ご期待ください。 また、発信内容やフォーマットについてのフィードバックも大歓迎です。 ぜひ忌憚のないご意見をお聞かせください。 (さらに公式アカウント @Metemcyber をフォローしていただけると関係メンバーみな泣いて喜びます) おまけ NA4Sec/Metemcyberチームではそれぞれ一緒に働く仲間を募集しています。 「脅威インテリジェンス」をキーワードに活躍の場を探している方、プロジェクトの理念に共感していただける方のご応募をお待ちしています。 Threat Intelligence Analyst / 脅威インテリジェンスアナリスト (NA4Sec) Threat Intelligence Engineer / 脅威インテリジェンスエンジニア (Metemcyber) NFLabs. ではオフェンシブセキュリティを中心としたエンジニア・マネージャーを募集中です。 また、Twitterやエンジニアブログでも情報を発信していますのでぜひ一度ご覧ください。 NFLabs. RECRUIT NFLabs. 公式Twitterアカウント( @NFLaboratories ) NFLabs. エンジニアブログ *7 *1 : Metemcyberについては こちらのブログ記事 でも紹介しています *2 : サイバー脅威インテリジェンス/Cyber Threat Intellgence(CTI):広義には「サイバー攻撃に関する情報を収集して整理・分析したもの」のこと。詳細は こちらの記事 でも解説しています *3 : Visionalistの件については 別のブログ記事 を公開していますので、詳細が気になる方はそちらをご覧ください *4 : IoC(Indicator of Compromise):IP・ドメイン・URL・ファイルハッシュなど、サイバー攻撃にみられる直接的な痕跡情報のこと *5 : スレットハンティング/Threat Hunting:自組織に未検知の脅威が侵入している可能性を前提に、能動的に脅威を探索すること *6 : アーティファクト分析/Artifact Analysis:コンピュータセキュリティインシデントに関わるアーティファクト(マルウエアや攻撃ツール、およびその関連技術)に関して、その実態や対策に関する技術的な調査、分析を行うこと(「 はじめてのJPCERT/CC 」より) *7 : strinsert1NaはNFLabs. エンジニアブログにも記事を寄稿しています → 「無名のセキュリティエンジニアがたった2本のブログ記事からSoftware Designで連載をすることになった( 非技術編 、 技術編 )」
アバター
はじめに イノベーションセンター Network Analytics for Security (以下、NA4Sec) プロジェクトリーダーの神田です。 NA4Secチームでは兄弟プロジェクトである Metemcyber ( @Metemcyber ) *1 と連携して、サイバー脅威インテリジェンス(以下、脅威インテリジェンス) *2 の配信をはじめました。 本記事ではこの取り組みについて、発案者であるチームメンバーの @strinsert1Na (以下、strinsert1Na) との対談も交えながら紹介します。 NA4Secプロジェクトとは? NA4Secプロジェクトは「NTTはインターネットを安心、安全にする社会的責務がある」を理念として、攻撃インフラの解明、撲滅を目指すプロジェクトです。 NTT Com イノベーションセンターのみならずNTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズ(以下、NFLabs. )からもメンバーが参画し、日夜攻撃インフラを追跡しています。 また、NTT Comグループにおける脅威インテリジェンスチームとしての側面も持ち合わせており、有事において脅威インテリジェンスを提供し、意思決定を支援することもあります。 一例としてアクセスログ解析サービス Visionalist で使用していたドメインが第三者によりドロップキャッチされた際には、不審なスクリプトが配置されたインフラを分析し、リスクを評価するとともに対応を提言しました。 *3 脅威インテリジェンス配信はじめました 今年度に入ってNFLabs. からマルウェアの静的解析を得意とする strinsert1Na がチームに参画しました。 ここからは脅威インテリジェンス配信をはじめた経緯やその狙いについて発案者 strinsert1Na との対談形式で紹介します。 経緯 神田(以下、kanda): 今年度からNA4Secチームに参画してもらったわけですが、もともと脅威インテリジェンス発信には興味があったのですか? strinsert1Na: もともと個人の趣味で標的型攻撃の検体を収集したり解析したりしていて、情報発信もしていました。 しかし、マルウェア解析はすごくリソースが必要な行為だし多くの企業ができるものでもない。脅威インテリジェンスサービスもすぐにIoC(Indicator of Compromise) *4 を公開するとは限らない。 公開情報から得られたマルウェアのIoCを信頼できる場所を使って発信できないだろうか、と考えていました。 kanda: そんなときに隣のMetemcyberチームがTwitterアカウントを使って発信する情報を探していることを知って、これはちょうどいいぞ、と。 strinsert1Na: はい(笑)。脅威インテリジェンス関連を扱っているプロジェクトだし、「そのまま脅威インテリジェンスを発信させていただきますかー」程度のノリでやってみることにしました。 狙い kanda: 脅威インテリジェンスを発信するにあたっての思いや狙いについて聞かせてください。 strinsert1Na: 私は「日本に関連する標的型攻撃を継続的にウォッチするアカウントが欲しいなぁ」と前々から思っていたので、この理想を自分で作ってみようかなと思いました。 標的型攻撃は時々刻々と手口が変化するため、「一度レポートを読んで終わり」ではなく継続的な観測と対策が必要不可欠だと私は考えています。しかし、これらの関連情報やIoCは初報以降、継続的に公開されることは多くなく、海外の脅威インテリジェンスサービスが日本関連の脅威インテリジェンスをリアルタイムに生成することも少ないです。 なので、本Twitterアカウントを監視するだけでなるべく多くの人が標的型攻撃のIoCをリアルタイムで観測し、具体的な防御に活かせるようになれば...というのが狙いです。 kanda: ないなら自らやってやろうというわけですね。情報発信するにあたって何か意識したことはありますか? strinsert1Na: 一般人が流し読むような”ポエム”ではなく、現場のエンジニアがインシデントハンドリング/スレットハンティング *5 で活用できるような”戦術(Tactical)脅威インテリジェンス”の側面から価値を提供することを一番意識しています。 なので、マルウェア/インテリジェンスアナリストがTweetを見ただけで脅威インテリジェンスの真偽を確認できて、IoCを収集できるような投稿にすることを心がけました。 本Twitterアカウントの情報を自動収集することで、即座に防御に対する意思決定に貢献できれば嬉しいなと思っています。 実際に情報発信してみて kanda: 実際に発信してみてどうでした? strinsert1Na: 「脅威インテリジェンス発信と言ってもTwitterに投稿するだけでしょ?」と軽く考えていましたが、使いやすいフォーマットやぱっと見でのわかりやすさ、140字でのまとめ方など創意工夫がかなり必要でした。 SHA256ハッシュを載せたいけど載せるとハッシュ値とC2の情報だけで140文字消化してしまうし、IoCとして有効かをきちんと判断できる証拠画像も欲しいし。 結果として絵文字を使ってビジュアルに表現しながら文字をなるべく削り、IoCベースですぐに脅威インテリジェンスとして消費できる形にまとめました。 自分の人生で最も1Tweetに時間がかかりましたが、納得のできる形になったのではないかと思っています。 🚨⚡ #LODEINFO (🇯🇵) C2: 🌍 http[:]//172.104.72[.]4/ (ASN: AS63949, ISP: Linode, LLC 🇯🇵) and 2 more (172.105.223[.]216, 45.77.28[.]124). Family: 🦠 LODEINFO (v0.6.2) MD5: 🔒 da1c9006b493d7e95db4d354c5f0e99f Sample: 📄 https://t.co/MvDcBjsfwV H/T to NA4Sec Team pic.twitter.com/YUhX1WCrWH — Metemcyber (@Metemcyber) 2022年8月5日 kanda: 情報発信後の反響や手応えは? strinsert1Na: 発信した情報をプロに拾ってもらってより詳細なアーティファクト分析 *6 の提供につながった事例が早速ありました。まだ2Tweetしかしていませんが、再現性のある脅威インテリジェンスはきちんと世の中の役に立っているのだということが実感できました。 Nice share! 🦠 HUI Loader (d839ab2c2ea8f082f98478cb552d6709) ☠️Create a service name "WxUpdateServiceInfo" ->spawn thread for load system.ini-> decoding sc and inject decoded sc into svchost.exe (1/4) https://t.co/zs3buBPm3e pic.twitter.com/yrJx99ePpF — m4n0w4r (@kienbigmummy) 2022年8月25日 今後の意気込み kanda: 最後に今後の意気込みをぜひ。 strinsert1Na: これからも公開情報をもとに継続的に観測を続け、IoCを発信していくつもりです。 JPCERT/CCやベンダのレポートからハンティングに使うルールを日々アップデートしながら、今後も最新の情報を追えるように努力していきます。 また、Twittterに載せる脅威インテリジェンスはなるべく自動収集できる形にできればいいなと考えていて、今後も決まったフォーマットで発信するつもりです。 みなさんの意見を取り入れつつ、改善してより使いやすい形にしていきたいと思っているので、公式アカウント( @Metemcyber )や私のアカウント( @strinsert1Na )にReplyでフィードバックをいただけると助かります。 おわりに 本記事では、NA4SecチームとMetemcyberチームで連携した脅威インテリジェンス発信について紹介しました。 今後も様々な脅威インテリジェンスを発信していく予定です。ご期待ください。 また、発信内容やフォーマットについてのフィードバックも大歓迎です。 ぜひ忌憚のないご意見をお聞かせください。 (さらに公式アカウント @Metemcyber をフォローしていただけると関係メンバーみな泣いて喜びます) おまけ NA4Sec/Metemcyberチームではそれぞれ一緒に働く仲間を募集しています。 「脅威インテリジェンス」をキーワードに活躍の場を探している方、プロジェクトの理念に共感していただける方のご応募をお待ちしています。 Threat Intelligence Analyst / 脅威インテリジェンスアナリスト (NA4Sec) Threat Intelligence Engineer / 脅威インテリジェンスエンジニア (Metemcyber) NFLabs. ではオフェンシブセキュリティを中心としたエンジニア・マネージャーを募集中です。 また、Twitterやエンジニアブログでも情報を発信していますのでぜひ一度ご覧ください。 NFLabs. RECRUIT NFLabs. 公式Twitterアカウント( @NFLaboratories ) NFLabs. エンジニアブログ *7 *1 : Metemcyberについては こちらのブログ記事 でも紹介しています *2 : サイバー脅威インテリジェンス/Cyber Threat Intellgence(CTI):広義には「サイバー攻撃に関する情報を収集して整理・分析したもの」のこと。詳細は こちらの記事 でも解説しています *3 : Visionalistの件については 別のブログ記事 を公開していますので、詳細が気になる方はそちらをご覧ください *4 : IoC(Indicator of Compromise):IP・ドメイン・URL・ファイルハッシュなど、サイバー攻撃にみられる直接的な痕跡情報のこと *5 : スレットハンティング/Threat Hunting:自組織に未検知の脅威が侵入している可能性を前提に、能動的に脅威を探索すること *6 : アーティファクト分析/Artifact Analysis:コンピュータセキュリティインシデントに関わるアーティファクト(マルウエアや攻撃ツール、およびその関連技術)に関して、その実態や対策に関する技術的な調査、分析を行うこと(「 はじめてのJPCERT/CC 」より) *7 : strinsert1NaはNFLabs. エンジニアブログにも記事を寄稿しています → 「無名のセキュリティエンジニアがたった2本のブログ記事からSoftware Designで連載をすることになった( 非技術編 、 技術編 )」
アバター
DevOpsプラットフォームの取り組みを紹介する6回目の記事です。 Qmonus Value Stream 開発チームの奥井 ( @HirokiOkui ) です。 連載第6回では、パラメータを効率的に管理するためのQmonus Value Streamの取り組みについて紹介します。 第3回 で解説したとおり、Qmonus Value StreamではInfrastructure as Code(以後IaC)およびCI/CDパイプラインを記述するためにCUE言語を用いています。 CUE言語は洗練されたデータ記述言語であり、CUE言語が有する型システムやプログラマビリティにより、宣言的マニフェストを高品質かつ効率的に記述することが可能になります。 しかしながら、CUE言語を用いても改善できない領域が存在します。 それは、パラメータです。 本記事では、IaCおよびCI/CDパイプラインの構築におけるパラメータの課題について掘り下げたのち、この解決のためにQmonus Value Streamが導入している3つのプラクティスを紹介します。 IaCとCI/CDにおけるパラメータの肥大化 IaCやCI/CDの技術領域では、非常に多くのパラメータを扱うことが要求されます。 クラウドインフラストラクチャの構成をコードで扱うということは、即ち管理対象の全てのクラウドリソースが要求(もしくは許可)している全てのパラメータを決定し、設定することと同義です。 試験環境や本番環境など複数の環境をIaCで扱う場合、ドメイン名やマシンリソースの割当など多くのパラメータで環境ごとに異なる値が必要になります。 環境ごとにマニフェスト全体を複製すると保守性が下がるため、ソフトウェアエンジニアリングのプラクティスに従いテンプレート化やモジュール化による再利用を行うと、それらのテンプレート・モジュールを利用するためにパラメータが必要になります。 CI/CDパイプラインにおいても同様で、ビルド・スキャン・テスト・デプロイなどの各タスクを実行するには、それぞれのタスクを実行するためのパラメータが必要です。 IaC・CI/CDにおけるモジュール化では、一般的なソフトウェア開発に比べてパラメータが増えやすい傾向にあります。 一般的なソフトウェア開発では、ドメインモデリングでよりよい抽象を見つけて Deep Module (インターフェースが狭くて実装が深いモジュール)を目指すことで、パラメータの数を減らすことができます。 一方でIaC・CI/CDは、扱う対象リソースがすでにDeep Moduleとなっており、モジュール化してもテンプレートマッピング程度のShallow Module(インターフェースが広くて実装が浅いモジュール)にしかならず、パラメータの数を大きく減らすことができません。 そのため、扱うリソースの数が増えて複雑度が増すほど、比例してパラメータの数が増えていきます。 加えて、近代的なソフトウェアエンジニアリングのプラクティスがパラメータの増大を助長します。 マイクロサービスアーキテクチャの普及により、サービスはコンテキストの境界で分割され、多数の小さなサービスを扱うことが増えています。 また、クラウドコンピューティングの普及で環境構築のコストが下がったこと、アジリティの高い開発のためテストに重点を置くようになり試験環境が複数必要になったことから、CI/CDパイプラインの中で複数の環境にデプロイすることが一般的になっています。 下図に示すように、開発初期はシンプルなマニフェストと少数のパラメータで事足りたのに、プロダクトの成長に伴いアプリケーションと環境の数が増加し、IaCマニフェストとパラメータが肥大化して管理が大変になったという方も多いのではないでしょうか。 IaCやCI/CDを宣言的マニフェストで管理したときに構成ファイルが増えすぎる問題は a Wall of YAML(YAMLの壁)と揶揄され、SREやリリースエンジニアリングに携わるものの大きな悩みとなっています。 YAMLの壁問題は、データ表現形式(およびデータ記述言語)の表現力・保守性と、パラメータ管理の2つに大きく分解できると考えます。 前者は、CUE言語の登場により飛躍的な向上を遂げました。 一方で後者については、未だ有効な解が見つかっていないのが実情です。 Tektonにおけるパラメータ管理の複雑さ Qmonus Value StreamではCI/CD実行基盤としてTektonを採用していますが、Tektonもパラメータ問題をまだ解けておらず、 最新のリリース で パラメータ流通の効率化の機能 が開発されるなど、試行錯誤を繰り返しています。 Tektonにおけるパラメータの問題とはどのようなものか、見てみましょう。 Tektonを用いて、Gitの変更をトリガとしてCIを実行するには、以下に示す5つのコンポーネントが必要となります(PipelineとTaskについては、 連載第5回 で詳細に解説しています)。 Events:GitプロバイダーなどによるWebhook。 Interceptorを記述することでデータを加工・拡張することが可能。 TriggerBinding:Eventsのペイロードのうち、TriggerTemplateに渡すパラメータを定義する。 TriggerTemplate:TriggerBindingから渡されるパラメータと、その他パラメータをハードコードして、Pipelineを実行するためのパラメータ一式を定義する。 Pipeline:実行可能なパイプライン定義。 Taskの有向非巡回グラフを持ち、順番にTaskを実行する。 実行時にパラメータを受け取り、Taskに引き渡す責務を持つ。 Task:Pipelineから実行パラメータを受け取り、Kubernetes Podを生成してタスクとして定義された処理を実行する。 Tektonは、上記のとおり責務ごとに異なるコンポーネントに分割されており、疎結合に作られていて入れ替えが容易なことから、高い柔軟性を具備しています。 Tektonコミュニティから提供される汎用的なTaskを組み入れつつ、オープンソースとして幅広いユースケースに対応するため、パイプラインの柔軟な構成が可能なこの設計は理に適っています。 一方で、柔軟性の高さ故に複雑であることがTektonの課題です。 上記の例を見ても分かる通り、Gitの変更をトリガとして単純なCIタスクを実行したいというだけでも、多くのコンポーネントが必要になります。 パラメータに注目してみると、その複雑さは一目瞭然です。 以下の図では、前述した5つのコンポーネントのうちパラメータマッピングの責務を持っているコンポーネントと、値の注入が可能なコンポーネントを示しました。 TriggerBinding、TriggerTemplate、Pipelineは、目的は異なれど全てパラメータマッピングの責務を持っています。 また、最終的に実行する対象となるTaskに対してパラメータの値を注入できる層は、Defaulting(値未指定時のデフォルト値の挿入)が可能なTaskも含めて4つもあります。 パラメータマッピング・注入層が複数あることにはメリットもあります。 下位の層でパラメータを凝集したりハードコードすることが可能となり、上位で必要なパラメータの数を減らせます。 シンプルなユースケースであれば、このアプローチは有効に機能するでしょう。 その一方で、層が多くて構成が複雑なため、継続的かつインクリメンタルな改善を繰り返すことで以下の問題が顕在化します。 パラメータがTaskに到達するまでの途中経路で、誤った値を挿入するリスクが高まります。 行き過ぎた汎化をして依存が多すぎる場合と、汎化せず複製された類似コンポーネントが多数ある場合のどちらでも発生します。 最終的にTaskで実行される際に使われる値を追いかけるのが困難になります。 想定と異なる設定値がTask実行時に使われていた場合に、パラメータマッピング・注入層が沢山あるため、上位のマニフェストを順に辿っていかないと、問題箇所に行き着くことができません。 Taskのパラメータが増えた場合に、外部から値を注入するには、Pipeline、TriggerTemplate…と値を注入する階層に至るまで再帰的に新規パラメータを追加する必要が生じます。 これは、PipelineやTriggerTemplateのレベルで汎化され凝集されている場合は問題にはなりませんが、複製のストラテジーを取っている場合は、大量のマニフェストを漏れなく修正する必要が生じます。 このようにIaC・CI/CDでは、パラメータマッピング・注入層を増やすことで得られるメリットよりも、複雑さの増加というデメリットの方が大きくなる傾向にあります。 リリースエンジニアやSREの努力と、コードにガバナンスを効かせるソフトウェアエンジニアリングのプラクティスによりこの問題に立ち向かうことはできますが、限界があります。 私達もDevOps基盤チームとして、増え続ける一方のCI/CD YAMLと戦い続けていましたが、そのあまりの多さと複雑さと管理コストの増大によって徐々に立ち行かなくなっていました。 IaC・CI/CDのパラメータの問題は、一般的なソフトウェアエンジニアリングとは背景・要件が異なるため、異なる解き方が求められていると考えています。 Qmonus Value Streamのアプローチ Qmonus Value Streamは、前述の問題に対して独自のアプローチで問題解決に取り組んでいます。 パラメータの増大と複雑さに立ち向かい、構成が複雑化しても保守性を損なわないために、我々が導入した3つのプラクティスについて紹介します。 1. パラメータマッピング・注入層を1つに限定する Qmonus Value Streamでは、パラメータマッピング・注入の層を1層に限定することで、透明性を高め、Taskに渡されるパラメータを追いかけることを容易にしています。 Qmonus Value Streamは以下の図に示すとおり、PipelineおよびTaskに加えて、Pipelineを実行するAssemblyLine、AssemblyLineに対してパラメータの値を注入するConfiguration RegistryやGit Eventsによって構成されます。 AssemblyLineとConfiguration Registryは、Qmonus Value Streamで独自に開発したコンポーネントです。 このように、前述したTektonのコンポーネントの例と同様に5つで構成されており、コンポーネント間の依存関係に差異はあるものの、有する責務も大きくは変わりません。 ここで、パラメータマッピング・注入の責務をもつコンポーネントに対して、Tektonの例と同様にマークを記述します。 以下の図を見ると、パラメータマッピング・注入の責務を持つコンポーネントがAssemblyLineに限定されていることがわかります。 Qmonus Value Streamでは、Cloud Native AdapterによりPipeline/Taskが自動生成され、この際にPipelineからTaskにどのようにパラメータをマッピングするかが一意に決定されるため、ユーザは関与できません。 また、Configuration RegistryやGit Eventsはパラメータを提供することだけを責務としており、Taskで実行されるパラメータの決定に関与する機能を持ちません。 唯一、Defaultingにより値を注入できる余地がPipelineに残されていますが、この値はAssemblyLineでのパラメータ未指定時の初期値として扱われます。 これにより、利用者は「AssemblyLineが有するパラメータマッピング・注入の機能を用いて、Pipelineが提供するパラメータのインターフェースをどのように満たすか」という問題にのみ集中すれば良くなります。 パラメータマッピングを行う箇所が1つに凝集されるため、1つの変更に伴うコード修正箇所を1箇所に限定できます。 AssemblyLineの層を見ればTaskで使われる値が一意に特定できるため、パラメータの調査も容易です。 3つ以上のパラメータマッピング・注入層が存在したTektonの例と比べると、大幅に複雑さが低減されています。 本設計の実現には、Cloud Native AdapterからPipeline/Taskマニフェストを自動生成する機構が重要な役割を果たしています。 こちらの詳細については、別の記事で紹介予定です。 2. スコープを限定し、関心を分離する パラメータマッピング・注入層をAssemblyLineの1層に限定したことで、パラメータの透明性を高めマッピングの複雑さを低減できましたが、そのトレードオフとして、1層で扱うパラメータが増大するという問題が生じます。 複数のパラメータマッピング・注入層がある場合は、下位の層でパラメータが決定できる場合はそこで注入してしまうことで、上位の層で注入するパラメータを減らすという選択を取ることができました (ただし、前述の通りこの選択は別の問題を引き起こすため、一概に良いものとは言えません)。 一方、パラメータマッピング・注入層を1層に限定する場合は、パラメータを挿入する余地がなくなるため、すべてのパラメータを1箇所で指定しなければなりません。 Qmonus Value Streamでは、Pipelineがタスクを実行するスコープ(以下、実行スコープ)に制限を導入し、同時に扱うコンテキストを減らすことで、この問題に対応します。 連載第2回 で言及したとおり、AssemblyLineは複数のStageによって構成され、1つのStageで1つのPipelineを実行します。 各Stageでは、1つのアプリケーションと1つの環境のペアをタスク実行対象として指定します。 このため、Qmonus Value Streamでは、1つのアプリケーションと1つの環境に対してタスク実行する粒度でPipelineを構成することが強制されます。 AssemblyLine Stageで実行対象とパイプラインを指定する例を以下に示します。 図中に記されている「AppA x Staging」や「AppA x Prod」が、Pipelineの実行スコープとなります。 Pipelineの実行スコープを1つのアプリケーションと1つの環境に限定することで、スコープを限定して関心を分離し、Pipelineごとに取り扱うパラメータの数を絞ります。 その結果、1層で扱うパラメータを小さく分割して管理できます。 Tekton自体にはこのような制約はなく、複数のアプリケーションや複数の環境を対象とした任意の大きさのPipelineを実行できます。 Tektonだけを用いてCI/CDパイプラインを構成する場合、Pipelineが複数のTaskをまとめて実行できる唯一の単位となりますので、このような構成を取ることが有効な場合もあるでしょう。 一方で、複数のアプリケーション・複数の環境を同時に1つのPipelineで扱うということは、多数のコンテキストを同時に扱うことになり、パラメータの増大に繋がります。 Qmonus Value StreamではPipelineの実行スコープが限定されますが、1つのワークフローとして束ねられるスコープには制限がなく、任意のスコープのワークフローを構成できます。 1つのアプリケーションと1つの環境を選択したPipelineをビルディングブロックとしてAssemblyLineを定義することで、複数のアプリケーションと複数の環境を対象としたワークフローを構成できるためです。 この仕組みにより、1つのワークフローで実行したい処理のスコープが広がった場合でも、Pipelineの実行スコープとパラメータを小さく維持できます。 3. スコープごとのパラメータセットとパラメータの自動注入 Pipelineの実行スコープを限定することで、パラメータマッピング・注入層を1つに限定したことによるパラメータの増加を限定的にできました。 この機構を活用して、更にパラメータ設定の負担を下げることを検討します。 Pipelineの実行スコープが1つのアプリケーションと1つの環境に限定されたので、パラメータを提供するConfiguration Registry側もこれにあわせて、アプリケーションと環境のペアの単位でパラメータセットを配備します。 これにより、Pipelineへのパラメータマッピング・注入がやりやすくなります。 Configuration Registryの機構により、GUIからアプリケーションや環境の設定をするだけで、設定した内容が自動で取り込まれ、アプリケーションと環境のペアの単位でのパラメータセットが構成されます。 下図に示すとおり、AssemblyLine StageでPipelineと実行対象を指定することは、満たすべきパラメータインターフェースと、その注入に利用できるパラメータセットを指定することに相当します。 加えて、AssemblyLine Stageの定義では、前述の通りパラメータのマッピングを柔軟に記述できます。 パラメータのマッピングは以下の形式で記述します。 params : - name : <Pipelineのインターフェースのキー> value : <`$(params.<パラメータセットのキー>)`で表される置換対象、を含む任意の文字列> パラメータマッピングが記述されているキーについては、記述の内容に従って置換評価された結果が格納されます。 パラメータマッピングが記述されていないキーについては、インターフェースで指定されているキーと同じキーを用いてパラメータセットを参照します。 以下の例では、Pipelineは appName と baseUrl の2つのパラメータを要求しています。 それに対して、AssemblyLine Stageでは baseUrl のみパラメータマッピングが記述されており、 $(params.domainName) が値に指定されています。 この場合、 baseUrl にはパラメータセットの domainName の値が注入され、 appName にはパラメータセットにおける同名の appName の値が注入されます。 キーが一致する場合の暗黙のパラメータ自動注入(以下、Implicit Binding)があることで、パラメータマッピングの記述量を減らすことができます。 TektonのPipeline/Taskのように、キーが一致している場合であってもすべて明示的な記述が必要だと、パラメータ数が多い場合には面倒です。 記述を忘れると、実行時にパラメータ不足でエラーとなり、余計な工数増に繋がります。 Implicit Bindingは、Pipelineの実行スコープを限定していることでより効果を発揮します。 Configuration Registryのパラメータセットは、決まった仕様・命名規則でパラメータを出力するため、Pipeline側をその命名規則に合わせて構成することで、明示的なマッピングが必要なパラメータの数を減らせます。 Configuration Registryに任意のKey-Valueを追加し、Pipelineのインターフェースにあわせてパラメータセットを拡張することで、同様にマッピングを減らせます。 また、パラメータマッピング・注入層を1層に集めていることで、パラメータの値の透明性を損なうことなくImplicit Bindingを活用できます。 パラメータマッピング・注入層が多数あるなかでパラメータ記述を省略すると、タスクで実際に利用される値を辿るのが更に難しくなります。 前述した通り、Tektonでも Propagated Parameters というPipelineからTaskへのパラメータ流通の効率化の機能を提供していますが、透明性を損なわず利用できる一部記法に限られており、効果が限定的です。 Qmonus Value Streamでは、パラメータマッピング・注入層を1層に限定しPipelineの実行スコープを限定しているからこそ、任意のケースでImplicit Bindingを活用しつつ、値の透明性と保守性を損なわず両立することが可能となっています。 おわりに DevOpsプラットフォームの取り組み連載の6回目の記事として、IaC・CI/CDの複雑化に伴うパラメータの増大という問題について紹介し、その解決のためにQmonus Value Streamが導入しているプラクティスを3つ紹介しました。 パラメータの問題は業界としてまだ発展途上であり、私達もさらなる改善に向けて試行錯誤を繰り返しています。 長い記事となってしまいましたが、私達がパラメータの問題をどのように捉え、その解決に向けてどのような取り組みを実践しているか、少しでも伝われば嬉しいです。 Qmonus Value Streamチームは、メンバ一人一人が、利用者であるアプリケーション開発者のために信念を持って活動を続けています。 プラットフォームを内製開発するポジション、プラットフォームを使ってNTTグループのクラウド基盤やCI/CDを構築支援するポジションそれぞれで 、一緒に働く仲間を募集していますので、興味を持たれた方は応募いただけると嬉しいです。 CI/CDプラットフォーム開発、ソフトウェアエンジニア NTTグループのクラウド基盤構築およびCI/CD導入、ソリューションアーキテクト 次はQmonus Value Streamの重要な構成要素であるAssemblyLineについて踏み込んでご紹介します。 お楽しみに!
アバター
サマリ SR-MPLS で構成されたネットワークにおいて、リンクの遅延を測定し Delay-Based TE を実現 IOS XR + Junos の Multi-vendor 環境での動作検証 この記事は Multi-AS Segment Routing 検証連載の第 7 回です。目次は こちら 概要 イノベーションセンターの竹中です。本記事ではリンク遅延値を用いる Delay-Based TE について説明します。 Delay-Based TE は各リンクの遅延時間をメトリックとして、E2E 遅延が最短となる経路を選択する経路制御手法です。遅延最短経路はシビアなリアルタイム性が求められる、例えばライブ配信や遠隔操作などでの活用が期待されます。 以降では、Segment Routing を用いたネットワークにおける Delay-Based TE の実現手法と、遅延の実測方法や Multi-vendor 環境における制約を紹介します。 Delay Measurement IGP Metric や TE Metric を利用する TE では、それぞれの Metric をインターフェースに静的に設定し、経路計算を行なっていました。 それに対し、Delay-Based TE を行うためにはリンクの遅延に対応する Delay Metric を動的に計測し、経路計算をする必要があります。 本節では遅延の計測方法について紹介します。 一般的な遅延計測プロトコルのユースケースには大きく分けて 2 パターンあります。 リンク遅延測定 接続されている 2 台のルーター間の遅延測定 Delay-based TE で用いる Delay Metric の算出に利用可能 IOS XR / Junos で実装済み E2E 遅延測定 E2E パスの遅延測定 まだ標準化されていないが独自機能により SR Policy 単位で測定可能 IOS XR にて実装有り 一般的に、遅延計測のためには Two-Way Active Measurement Protocol (TWAMP) と呼ばれる技術が利用されます。 本記事では Multi-vendor における TWAMP を利用したリンク遅延測定の検証と、Delay-Based TE の検証を実施します。 TWAMP の仕組み TWAMP は、 RFC5357 で標準化されている、IP ネットワークにおいてリンクの単方向遅延や双方向遅延を測定できるプロトコルです。 計測用のテストセッションを確立するための Server / Control-Client と、実際に計測用のパケットを交換する Session-Sender / Session-Reflector の 4 つの要素で構成されます。実装上は Server と Session-Reflector の組み合わせで responder として、Control-Client と Session-Sender の組み合わせで controller として扱われることもあります。 TWAMP を用いた遅延計測のおおまかな流れは次の通りです。 Control-Client が TWAMP の well-known port (もしくは設定されたポート) で TCP コネクションを開始し、Server が greeting message を応答する Control-Client が通信モードと、必要に応じて完全性保護と暗号化のための情報を応答する。Server はモード受け入れの応答をする Control-Client がテストセッションを要求し、セッションを開始する Session-Sender と Session-Reflector の間でテストパケットを交換し計測する セッションを終了する なお、セッション管理を排除し簡略化された TWAMP Light と呼ばれるプロトコルも定義されています。こちらは Server / Control-Client / Session-Sender を持つ controller と、Session-Reflector のみを持つ responder から構成されます。TWAMP Light においては responder はセッション情報を持たず、テストパケットに応答するのみになります。 補足として、TWAMP 検証の中で IOS XR / Junos において確認できた制約を記載します。 なお各バージョン番号は我々が検証に使用したバージョンです。 IOS XR NCS55A2 (IOS XR 7.4.1) Single-vendor、Multi-vendor 問わず controller / responder として動作確認可能 ASR9901 (IOS XR 7.4.1) Single-vendor、Multi-vendor 問わず controller / responder として動作確認可能 Cisco 8201 (IOS XR 7.5.1) well-known port である 862 が利用不可。利用可能ポートは XRv9000 (IOS XR 7.4.1) responder としては 利用可能だが、controller としては利用不可 Junos MX204 (Junos 21.2R1.10) Single-vendor、Multi-vendor 問わず controller / responder として動作確認可能。well-known port 以外の利用可能ポートは MX10003 (Junos 21.2R1.10) Single-vendor、Multi-vendor 問わず controlle / responder として動作確認可能。well-known port 以外の利用可能ポートは PTX10001-36MR (Junos 21.2R1.15-EVO) Single-vendor、Multi-vendor 問わず controlle / responder として動作確認可能。well-known port 以外の利用可能ポートは vMX (Junos 21.3R1.9) Single-vendor において controller / responder として動作確認可能。Multi-vendor については未検証。 特にポート番号の制約に着目すると Cisco 8201 と Juniper 機器群の間で相互接続できないことが予想されます。そして実際に確認したところ測定できませんでした。 Delay Metric の広告 Delay Metric を TE で利用するためには、SR Policy を管理するコントローラーや ingress PE といった、経路を計算する機器が各リンクの Delay Metric を保持している必要があります。広告方法としては IS-IS や OSPF などの IGP において TLV によってお互いに広告する方法や、BGP-LS を用いて AS を越えて広告する方法が挙げられます。 IGP では TE 向け拡張が RFC で定義され 1 2 、その中に Delay Metric の広告用 TLV も含まれています。 本連載では一貫して IGP に IS-IS を用いた AS 内の経路学習を行なってるため、本検証での Delay Metric の広告も IS-IS で行います。 Delay Metric を用いた TE の実装パターン Delay Metric を用いた TE の実装方法は次の 2 種類があります。 SR Policy の dynamic path による TE Delay Metric をリンクコストの制約として計算した Flex-Algorithm による TE SR Policy の dynamic path とは 第 5 回記事 の検証でも利用した、SR Policy で指定したメトリックを用いて Segment List を計算する方法です。 Flex-Algorithm に関する説明と検証例は 第 6 回記事 にて行なったので、実装方法などはそちらを参照ください。 以降の検証章ではリンク遅延の計測から IGP での広告、Delay Metric を用いた SR Policy の作成と動作確認を行います。 TWAMP 動作検証 本記事での検証は、遅延の計測・広告と TE 実装で章を分けて説明します。 まず本章では TWAMP を用いたリンク遅延測定と IS-IS で測定値を広告する設定例を紹介します。 動作検証環境は下図の通りです。各ルーター間のリンクは直結です。 なお、IGP にて広告するリンク間遅延の測定手法として Cisco / Juniper ともに TWAMP Light 3 形式で実装されているため、TWAMP Light の設定方法について紹介します。 設定と確認 TWAMP のサーバー、クライアント、IS-IS で広告するための設定を追加します。 なお IS-IS の基本設定の記載は省略します。基本設定は 第 1 回記事 を参照ください。 rt01 (IOS XR) ipsla responder twamp-light test-session 1 local-ip 10.1.2.1 local-port 862 remote-ip 10.1.2.2 remote-port any vrf default ! ! ! performance-measurement interface TenGigE0/0/0/8 next-hop ipv4 10.1.2.2 delay-measurement ! ! ! rt02 (Junos) set services rpm twamp server light port 862 set protocols isis interface xe-0/1/0.0 delay-measurement 上記設定を全て投入後、各ルーターにて計測が行われていること、IS-IS で広告していることを確認します。 rt01 (IOS XR) RP/0/RSP0/CPU0:rt01#show performance-measurement interfaces tenGigE 0/0/0/8 brief Tue Aug 16 20:05:55.144 JST ---------------------------------------------------------------------------------------------------------------------------------------------------------------- 0/0/CPU0 ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Interface Name Tx/Rx Avg/Min/Max/Variance (uSec) -------------------------------------------------------------------------------------------------------------------------------------------------------------- TenGigE0/0/0/8 9/9 85/75/102/10 RP/0/RSP0/CPU0:rt01#show isis database rt01.00-00 verbose Wed Aug 24 17:27:37.673 JST IS-IS 1 (Level-2) Link State Database LSPID LSP Seq Num LSP Checksum LSP Holdtime/Rcvd ATT/P/OL rt01.00-00 * 0x000097b7 0x4c8f 991 /* 0/0/0 (snip) Metric: 10 IS-Extended rt02.00 Local Interface ID: 26, Remote Interface ID: 349 Interface IP Address: 10.1.2.1 Neighbor IP Address: 10.1.2.2 Affinity: 0x00000000 Physical BW: 10000000 kbits/sec Reservable Global pool BW: 0 kbits/sec Global Pool BW Unreserved: [0]: 0 kbits/sec [1]: 0 kbits/sec [2]: 0 kbits/sec [3]: 0 kbits/sec [4]: 0 kbits/sec [5]: 0 kbits/sec [6]: 0 kbits/sec [7]: 0 kbits/sec Admin. Weight: 10 Ext Admin Group: Length: 32 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 Physical BW: 10000000 kbits/sec Link Average Delay: 79 us Link Min/Max Delay: 52/114 us Link Delay Variation: 18 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:24009 rt02 (Junos) user@rt02> show services rpm twamp client Connection Session Sender Sender Reflector Reflector Name Name address port address port __r__2 __r__3 10.1.2.2 59823 10.1.2.1 862 user@rt02> show services rpm twamp client probe-results control-connection __r__2 Owner: __r__2, Test: __r__3 TWAMP-Server-Status: Light, Number-Of-Retries-With-TWAMP-Server: 0 Reflector address: 10.1.2.1, Reflector port: 862, Sender address: 10.1.2.2, sender-port: 52980 Test size: 10 probes Probe results: Response received Probe sent time: Tue Aug 16 20:08:50 2022 Probe rcvd/timeout time: Tue Aug 16 20:08:50 2022 Rtt: 138 usec, Egress jitter: 28 usec, Ingress jitter: -44 usec, Round trip jitter: -137 usec Egress interarrival jitter: 99 usec, Ingress interarrival jitter: 105 usec, Round trip interarrival jitter: 137 usec (snip) user@rt02> show isis database rt02.00-00 extensive IS-IS level 1 link-state database: IS-IS level 2 link-state database: rt02.00-00 Sequence: 0x1d6ea, Checksum: 0xf3cb, Lifetime: 1146 secs (snip) TLVs: (snip) IS extended neighbor: rt01.00, Metric: default 10 SubTLV len: 51 IP address: 10.1.2.2 Neighbor's IP address: 10.1.2.1 Local interface index: 349, Remote interface index: 26 Unidirectional link delay: 76 Min unidirectional link delay: 53 Max unidirectional link delay: 91 Unidirectional delay variation: 104 P2P IPV4 Adj-SID - Flags:0x30(F:0,B:0,V:1,L:1,S:0,P:0), Weight:0, Label: 45 P2P IPv4 Adj-SID: 45, Weight: 0, Flags: --VL-- また、各ルーターにて対向から IS-IS でリンク遅延の広告が行われていることを確認します。 rt01 (IOS XR) RP/0/RSP0/CPU0:rt01#show isis database rt02.00-00 verbose Tue Aug 16 20:14:16.022 JST IS-IS 1 (Level-2) Link State Database LSPID LSP Seq Num LSP Checksum LSP Holdtime/Rcvd ATT/P/OL rt02.00-00 0x0001b8fc 0xf246 1049 /1198 0/0/0 (snip) Metric: 10 IS-Extended rt01.00 Interface IP Address: 10.1.2.2 Neighbor IP Address: 10.1.2.1 Local Interface ID: 349, Remote Interface ID: 26 Link Average Delay: 76 us Link Min/Max Delay: 60/107 us Link Delay Variation: 119 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:45 (snip) rt02 (Junos) user@rt02> show isis database rt01.00-00 extensive IS-IS level 1 link-state database: IS-IS level 2 link-state database: rt01.00-00 Sequence: 0x9455, Checksum: 0x1a27, Lifetime: 630 secs (snip) TLVs: (snip) Extended IS Reachability TLV, Type: 22, Length: 159 IS extended neighbor: rt02.00, Metric: default 10 SubTLV len: 148 Local interface index: 26, Remote interface index: 349 IP address: 10.1.2.1 Neighbor's IP address: 10.1.2.2 Administrative groups: 0 <none> Maximum bandwidth: 10Gbps Maximum reservable bandwidth: 0bps Current reservable bandwidth: Priority 0 : 0bps Priority 1 : 0bps Priority 2 : 0bps Priority 3 : 0bps Priority 4 : 0bps Priority 5 : 0bps Priority 6 : 0bps Priority 7 : 0bps Traffic engineering metric: 10 Unknown sub-TLV type 252, length 32 Maximum bandwidth: 10Gbps Unidirectional link delay: 79 Min unidirectional link delay: 52 Max unidirectional link delay: 114 Unidirectional delay variation: 18 P2P IPV4 Adj-SID - Flags:0x30(F:0,B:0,V:1,L:1,S:0,P:0), Weight:0, Label: 24009 P2P IPv4 Adj-SID: 24009, Weight: 0, Flags: --VL-- (snip) 考察 Cisco ルーター同士(rt01-rt03 間)、Juniper ルーター同士(rt02-rt04 間)で TWAMP を用いてリンク遅延計測をしたところ、下記の計測結果に表示されているとおり遅延値に大きく差がありました。これは推測ですが、TWAMP の Session-Reflector が動作する場所の違いによるものだと考えられます。 ※ このため、現状 Multi-vendor 環境において TWAMP によるリンク遅延実測値を用いた Delay-Based TE は必ずしも実態としての最短遅延パスを通るとは限らないと思われます。 Cisco ルーター間のリンク遅延計測結果 RP/0/RSP0/CPU0:rt01#show performance-measurement interfaces hundredGigE 0/0/0/20 brief Thu Aug 18 16:24:37.574 JST ---------------------------------------------------------------------------------------------------------------------------------------------------------------- 0/0/CPU0 ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Interface Name Tx/Rx Avg/Min/Max/Variance (uSec) -------------------------------------------------------------------------------------------------------------------------------------------------------------- HundredGigE0/0/0/20 6/6 3/3/4/0 Juniper ルーター間のリンク遅延計測結果 user@rt02> show services rpm twamp client Connection Session Sender Sender Reflector Reflector Name Name address port address port __r__0 __r__1 10.1.8.1 58574 10.1.8.2 862 __r__2 __r__3 10.1.2.2 50830 10.1.2.1 862 user@rt02> show services rpm twamp client probe-results control-connection __r__0 Owner: __r__0, Test: __r__1 TWAMP-Server-Status: Light, Number-Of-Retries-With-TWAMP-Server: 0 Reflector address: 10.1.8.2, Reflector port: 862, Sender address: 10.1.8.1, sender-port: 58042 Test size: 10 probes Probe results: Response received Probe sent time: Thu Aug 18 16:25:39 2022 Probe rcvd/timeout time: Thu Aug 18 16:25:39 2022 Rtt: 449 usec, Egress jitter: 97 usec, Ingress jitter: 29 usec, Round trip jitter: 169 usec Egress interarrival jitter: 102 usec, Ingress interarrival jitter: 91 usec, Round trip interarrival jitter: 92 usec (snip) 次章では広告されたリンク遅延の値を利用して Delay-Based TE を実装します。 Delay-Based TE 動作検証 本章では IS-IS によって広告されたリンク遅延を用いて TE を実装するための設定例を紹介します。 なお、本記事では SR Policy の作成に On-Demand Next-Hop(ODN)と呼ばれる手法を利用します。 ODN は SR Policy を動的に生成可能な仕組みです。SR Policy の生成条件となる color と、パラメータである Segment List(explicit or dynamic)を定義しておくことで、 IBGP で対象となる経路を受け取った際に BGP nexthop を endpoint とした SR Policy を自動で生成できます。 ODN を利用することで、複数の egress PE がある場合でも endpoint ごとの SR Policy の設定が不要となります。 トポロジー 下記のようなトポロジーで検証します。本章においては、リンク遅延(Delay Metric)は TE のわかりやすさのため図の通りに手動で設定します。 リンク遅延は実測値ベースのため往路と復路で非対称になり得ます。本検証では PE1 -> P1、P2 -> PE2 の片方向のみ値を 100 とし、他の Delay Metric は 10 とします。 非対称な Metric によって E2E の経路も非対称となることを確認します。 このように Delay Metric を設定したネットワークにおいて Delay-Based TE を実装した場合、次の図のような経路となることが想定されます。PE1 -> PE2 への転送時には Delay Metric の大きいリンクを避ける経路となり、PE2 -> PE1 への転送時にはリンクの Delay Metric が全て等しく最短経路となるためです。 物理構成や基本的な interface、IGP、BGP の設定は 第 1 回記事 をご参照ください。 検証において利用する機器は下記の通りです。 PE1、P1: Cisco IOS XR 7.4.1 PE2、P2: Junos 21.3R1.9 Delay Metric の手動設定と確認 リンク遅延の実測値を利用した場合 TE の様子が分かりにくくなるため、各ルーターにて手動で Delay Metric を付与します。 PE1 (IOS XR) performance-measurement interface GigabitEthernet0/0/0/0 delay-measurement advertise-delay 100 ! ! interface GigabitEthernet0/0/0/1 delay-measurement advertise-delay 10 ! ! ! P1 (IOS XR) performance-measurement interface GigabitEthernet0/0/0/0 delay-measurement advertise-delay 10 ! ! interface GigabitEthernet0/0/0/1 delay-measurement advertise-delay 10 ! ! interface GigabitEthernet0/0/0/2 delay-measurement advertise-delay 10 ! ! ! P2 (Junos) set protocols isis interface ge-0/0/0.0 delay-metric 10 set protocols isis interface ge-0/0/1.0 delay-metric 100 set protocols isis interface ge-0/0/2.0 delay-metric 10 PE2 (Junos) set protocols isis interface ge-0/0/0.0 delay-metric 10 set protocols isis interface ge-0/0/1.0 delay-metric 10 Delay Metric が IS-IS にて広告されていることを PE1 にて確認します。 RP/0/RP0/CPU0:PE1#show isis database verbose Wed Aug 17 19:34:32.686 JST IS-IS 1 (Level-2) Link State Database LSPID LSP Seq Num LSP Checksum LSP Holdtime/Rcvd ATT/P/OL PE1.00-00 * 0x0000068a 0xcd94 1016 /* 0/0/0 (snip) Metric: 10 IS-Extended P1.00 Local Interface ID: 7, Remote Interface ID: 7 Interface IP Address: 10.0.0.1 Neighbor IP Address: 10.0.0.2 Physical BW: 1000000 kbits/sec Link Average Delay: 100 us Link Min/Max Delay: 100/100 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:24003 Metric: 10 IS-Extended P2.00 Local Interface ID: 8, Remote Interface ID: 333 Interface IP Address: 10.0.1.1 Neighbor IP Address: 10.0.1.2 Physical BW: 1000000 kbits/sec Link Average Delay: 10 us Link Min/Max Delay: 10/10 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:24001 Hostname: PE1 P1.00-00 0x00000680 0x67b0 1197 /1200 0/0/0 (snip) Metric: 10 IS-Extended PE1.00 Local Interface ID: 7, Remote Interface ID: 7 Interface IP Address: 10.0.0.2 Neighbor IP Address: 10.0.0.1 Physical BW: 1000000 kbits/sec Link Average Delay: 10 us Link Min/Max Delay: 10/10 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:24005 Metric: 10 IS-Extended PE2.00 Local Interface ID: 8, Remote Interface ID: 333 Interface IP Address: 10.0.2.1 Neighbor IP Address: 10.0.2.2 Physical BW: 1000000 kbits/sec Link Average Delay: 10 us Link Min/Max Delay: 10/10 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:24003 Metric: 10 IS-Extended P2.00 Local Interface ID: 9, Remote Interface ID: 335 Interface IP Address: 10.0.4.1 Neighbor IP Address: 10.0.4.2 Physical BW: 1000000 kbits/sec Link Average Delay: 10 us Link Min/Max Delay: 10/10 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:24001 PE2.00-00 0x000006a3 0x86aa 1050 /1200 0/0/0 (snip) Metric: 10 IS-Extended P1.00 Interface IP Address: 10.0.2.2 Neighbor IP Address: 10.0.2.1 Local Interface ID: 333, Remote Interface ID: 8 Link Average Delay: 10 us Link Min/Max Delay: 10/10 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:17 Metric: 10 IS-Extended P2.00 Interface IP Address: 10.0.3.1 Neighbor IP Address: 10.0.3.2 Local Interface ID: 334, Remote Interface ID: 334 Link Average Delay: 10 us Link Min/Max Delay: 10/10 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:16 (snip) P2.00-00 0x000006aa 0xca39 1066 /1198 0/0/0 (snip) Metric: 10 IS-Extended PE1.00 Interface IP Address: 10.0.1.2 Neighbor IP Address: 10.0.1.1 Local Interface ID: 333, Remote Interface ID: 8 Link Average Delay: 10 us Link Min/Max Delay: 10/10 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:17 Metric: 10 IS-Extended PE2.00 Interface IP Address: 10.0.3.2 Neighbor IP Address: 10.0.3.1 Local Interface ID: 334, Remote Interface ID: 334 Link Average Delay: 100 us Link Min/Max Delay: 100/100 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:16 Metric: 10 IS-Extended P1.00 Interface IP Address: 10.0.4.2 Neighbor IP Address: 10.0.4.1 Local Interface ID: 335, Remote Interface ID: 9 Link Average Delay: 10 us Link Min/Max Delay: 10/10 us Link Delay Variation: 0 us ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:18 (snip) ODN による SR Policy の定義 各 PE にて ODN を設定します。 PE1 (IOS XR) segment-routing traffic-eng on-demand color 100 dynamic metric type latency ! ! ! ! ! PE2 (Junos) set routing-options dynamic-tunnels ODN-100 spring-te source-routing-path-template ODN-100-TMP color 100 set routing-options dynamic-tunnels ODN-100 spring-te destination-networks 0.0.0.0/0 set protocols source-packet-routing compute-profile DYNAMIC-DELAY metric-type delay set protocols source-packet-routing source-routing-path-template ODN-100-TMP primary odn_path compute DYNAMIC-DELAY BGP 経路に color 100 を付加することで動的に SR Policy が作成されます。 color の付加方法は 第4回記事 を参照ください。 color 付加後、Delay-Based TE のための SR Policy が作成されていることが確認できます。 PE1 (IOS XR) RP/0/RP0/CPU0:PE1#show segment-routing traffic-eng policy Wed Aug 17 21:02:43.435 JST SR-TE policy database --------------------- Color: 100, End-point: 10.255.0.3 Name: srte_c_100_ep_10.255.0.3 Status: Admin: up Operational: up for 00:00:49 (since Aug 17 21:01:53.919) Candidate-paths: Preference: 200 (BGP ODN) (active) Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) Metric Type: LATENCY, Path Accumulated Metric: 30 16003 [Prefix-SID, 10.255.0.4] 16001 [Prefix-SID, 10.255.0.2] 16002 [Prefix-SID, 10.255.0.3] Preference: 100 (BGP ODN) Requested BSID: dynamic PCC info: Symbolic name: bgp_c_100_ep_10.255.0.3_discr_100 PLSP-ID: 1 Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (pce) (invalid) Metric Type: NONE, Path Accumulated Metric: 0 Attributes: Binding SID: 24007 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no PE2 (Junos) user@PE2> show spring-traffic-engineering lsp detail Name: 10.255.0.1:64:dt-srte-ODN-100 Tunnel-source: Dynamic Tunnel Module(DTM) Tunnel Forward Type: SRMPLS Tunnel-template: ODN-100-TMP To: 10.255.0.1-100<c> State: Up Path: odn_path Path Status: NA Outgoing interface: NA Auto-translate status: Disabled Auto-translate result: N/A Compute Status:Enabled , Compute Result:success , Compute-Profile Name:DYNAMIC-DELAY Total number of computed paths: 1 Computed-path-index: 1 BFD status: N/A BFD name: N/A TE metric: 110, IGP metric: 20 Delay metrics: Min: 20, Max: 20, Avg: 20 Metric optimized by type: Minimum-Delay computed segments count: 1 computed segment : 1 (computed-node-segment): node segment label: 16001 router-id: 10.255.0.1 疎通確認 traceroute を用いて TE によるパケットの往復の転送経路を確認します。 リンク遅延は実測値ベースのため往路と復路で非対称になり得ます。本検証では PE1 -> P1、P2 -> PE2 の片方向のみ値を 100 としたので PE1 -> PE2 の経路のみ PE1 - P1 と P2 - PE2 のリンクを避ける経路となるはずです。 PE1 -> PE2 RP/0/RP0/CPU0:PE1#traceroute 192.168.1.254 vrf 100 Thu Aug 18 14:48:00.861 JST Type escape sequence to abort. Tracing the route to 192.168.1.254 1 10.0.1.2 [MPLS: Labels 16002/16003/16 Exp 0] 18 msec 3 msec 7 msec 2 10.0.4.1 [MPLS: Labels 16003/16 Exp 0] 23 msec 20 msec 19 msec 3 192.168.1.254 12 msec 2 msec 8 msec PE2 -> PE1 user@PE2> traceroute 192.168.0.254 routing-instance 100 traceroute to 192.168.0.254 (192.168.0.254), 30 hops max, 52 byte packets 1 10.0.3.2 (10.0.3.2) 2.729 ms 1.820 ms 1.731 ms MPLS Label=16001 CoS=0 TTL=1 S=0 MPLS Label=24004 CoS=0 TTL=1 S=1 2 10.0.1.1 (10.0.1.1) 14.716 ms * 11.153 ms 次の図のように、P1 -> PE1、PE2 -> P2 のリンク遅延を大きくすることで PE2 -> PE1 の経路も PE1 -> PE2 と同じ経路を通ることを確認します。 PE2 [edit] user@PE2# set protocols isis interface ge-0/0/1.0 delay-metric 100 [edit] user@PE2# show | compare [edit protocols isis interface ge-0/0/1.0] - delay-metric 10; + delay-metric 100; P1 performance-measurement interface GigabitEthernet0/0/0/0 delay-measurement <- advertise-delay 10 +> advertise-delay 100 ! ! ! end 再度 PE2 -> PE1 で traceroute を行うと経路が変わっていることが確認できます。 PE2 -> PE1 user@PE2> traceroute 192.168.0.254 routing-instance 100 traceroute to 192.168.0.254 (192.168.0.254), 30 hops max, 52 byte packets 1 10.0.2.1 (10.0.2.1) 8.389 ms 8.118 ms 10.108 ms MPLS Label=16004 CoS=0 TTL=1 S=0 MPLS Label=16001 CoS=0 TTL=1 S=0 MPLS Label=24004 CoS=0 TTL=1 S=1 2 10.0.4.2 (10.0.4.2) 2.344 ms 2.011 ms 1.516 ms MPLS Label=16001 CoS=0 TTL=1 S=0 MPLS Label=24004 CoS=0 TTL=2 S=1 3 10.0.1.1 (10.0.1.1) 13.928 ms * 13.418 ms まとめ 本記事では、遅延計測技術である TWAMP と Delay Metric を用いて経路制御をする Delay-Based TE について紹介しました。 また Multi-vendor での TWAMP Light による遅延計測や計測結果の広告と、TE の実装について動作検証しました。 特に現状では遅延計測に対応できない機器の組み合わせがありました。 次回の記事では宛先 prefix 以外をトリガにした SR Policy の適用方法について紹介予定です。 (2022/09/16 追記) 公開しました: [Multi-AS Segment Routing 検証連載 #8] SR Policy の適用方法と活用 IS-IS TE Metric Extensions: https://tex2e.github.io/rfc-translater/html/rfc8570.html ↩ OSPF TE Metric Extensions: https://tex2e.github.io/rfc-translater/html/rfc7471.html ↩ TWAMP Light: https://www.rfc-editor.org/rfc/rfc5357.html#appendix-I ↩
アバター
目次 目次 はじめに MMDetectionとは MMDeployとは 実験内容 利用したモデル 計測結果 まとめ はじめに イノベーションセンターの加藤です。普段はコンピュータビジョンの技術開発やAI/MLシステムの検証に取り組んでいます。 今年登場したJetsonの最新版モデル「Jetson AGX Orin」は、前世代である「Jetson AGX Xavier」シリーズの最大8倍のパフォーマンス 1 、ビジョンや自然言語処理など様々な学習済みモデルにおいては最大5倍の高速化 2 が謳われており、エッジデバイス上で動かせるAIアプリケーションの幅がかなり広がりそうです。普段メディアAIに取り組んでいる私としてはどのレベルまでの物体検出モデルがエッジ上で動かせるようになったのかが気になりました。そこで本記事ではMMDetectionの提供する物体検出モデルを NVIDIA TensorRT を用いて各デバイス上で最適化し、COCO Val 2017データセットを用いてモデルの推論速度を調査しました。 MMDetectionとは https://github.com/open-mmlab/mmdetection MMDetectionは OpenMMLab の提供する物体検出ツールボックスです。ソースコードだけでなく学習済みモデルも公開されているので、さまざまなモデルを使って物体検出の推論ができます。 このツールボックスは PyTorch ベースですが、各Jetsonデバイスの性能をめいっぱい活かすために、MMDeployというツールを用いて学習済みモデルをTensorRTモデルに変換してから速度を計測しました。 MMDeployとは https://github.com/open-mmlab/mmdeploy MMDeployはMMDetection同様にOpenMMLabが提供するツールで、OpenMMLab製のモデルを ONNX やTensorRTなどのバックエンド用に変換して利用できます。物体検出のMMDetectionだけでなく、分類( MMClassification )やセグメンテーション( MMSegmentation )、姿勢推定( MMPose )などのモデルにも対応しています。 実は以前までは内部で使われているビルドオプションの都合上Orinに対応したバイナリがビルドできずMMDeployをOrinで使うにはひと手間必要でしたが、私の プルリクエスト によりversion 0.6以降はデフォルトの設定で利用できるようになっています :) 実験内容 使用したデバイスは以下の2点です。 Jetson AGX Xavier Jetson AGX Orin 32GB いずれのデバイスにも NVIDIA JetPack SDK 5.0を導入しています。 評価データセットは5000枚の画像からなる COCO Val 2017 を使いました。 そしてMMDeployは変換したモデルの ベンチマークを計測することができる ので、本記事ではこの機能を用いてXavierとOrinそれぞれにおけるTensorRTモデルの推論速度(FPS)を計測しました。 実験コードは こちら にアップロードしていますので参考にしてください。 利用したモデル 実験に使用したモデルの一覧を示します。configリンクは全てMMDetectionのリポジトリ内のコンフィグファイルを指しており、そのコンフィグファイルが置いてあるディレクトリのREADMEでモデルの概要を見ることができます。 モデル名(バックボーン) 入力サイズ box AP link YOLOV3(DarkNet53) 320x320 27.9 config SSD300(VGG16) 300x300 25.5 config RetinaNet(ResNet50) 800x1333 36.5 config FCOS(ResNet50) 800x1333 36.6 config FSAF(ResNet50) 800x1333 37.4 config YOLOX-s 640x640 40.5 config Faster R-CNN (ResNet50) 800x1333 37.4 config ATSS (ResNet50) 800x1333 39.4 config Cascade R-CNN (ResNet50) 800x1333 40.4 config GFL(ResNet50) 800x1333 40.2 config Cascade Mask R-CNN (ResNet50) 800x1333 41.2 config Mask R-CNN (ResNet50) 800x1333 38.2 config Mask R-CNN (Swin Transformer) 800x1333 42.7 config 計測結果 Swin TransformerをバックエンドにしたMask R-CNNはまだまだOrin上でも厳しそうですが、その他のモデルでは半精度化することによって20-30FPSとほぼリアルタイムに動作させられることがわかりました。 AGX Xavierと比較すると、どのモデルにおいても大きな速度向上が見られ、最小でも2.5倍、最大で10倍近い高速化が見られます。 一方で単純計算なら処理速度が2倍になるはずの半精度化(fp16)の恩恵はAGX Xavierが大きく、AGX Orinではあまり速度が伸びませんでした。メモリアクセスが間に合っておらず性能を出しきれていないというのが仮説の1つとして挙げられますが、より詳細な調査が必要そうです。 まとめ 本記事では様々な物体検出モデルをAGX XavierとAGX Orinで動かし、Jetson最新モデルのAGX Orinがどれくらい性能向上したかを調査しました。すると今までエッジ上ではリアルタイム処理に使えなかった高解像度・高性能なモデルが、AGX Orinでは現実的な処理速度を実現していることがわかりました。これは今までのエッジ用AIアプリケーションの性能向上につながるだけでなく、遠くの物や小さいものをリアルタイムに検知する必要があるタスクにおいてもスタンドアローンで処理可能になることも考えられ、自動運転のようにカメラに写る物体の遠近のレンジが大きい環境や、群衆の混雑度監視のように広い範囲に細々とした物体が大量に写る環境などといった解像度がネックになるタスクでもエッジAIが戦えるようになりそうです。 https://www.nvidia.com/ja-jp/autonomous-machines/embedded-systems/jetson-orin/ ↩ https://youtu.be/hHDZV1V4zq8?t=379 ↩
アバター
目次 はじめに SDP開発とは SDP開発とは SSS/SDPフレームワーク ICGW-SDP基盤 全体構成 CI/CD ICGWの可視化 APIリクエスト数 リソース利用状況 おわりに はじめに こんにちは、5G & IoT部/IoTサービス部門のIoT Connect Gateway (ICGW)サービス開発チームの岩田です。 我々のチームでは2021年度下期に私の主導のもと、ICGWのSDP開発というものを行い、 Smart Data Platform (SDPF)ポータル対応 およびSDPF上で展開している他のサービスとの自動連携を実現しました。 本記事ではこのICGWのSDP開発に焦点を当てて、開発背景や全体構成ならびにCI/CDを紹介します。 また、ICGWの可視化関連の情報も合わせて掲載しますので、今後の開発のご参考になれば幸いです。 ICGWのサービス自体やユースケースに関して、過去の記事で紹介していますのでご参照ください。 IoT Connect Gateway を使ってみた 第1回 〜ICGWのご紹介〜 IoT Connect Gateway を使ってみた 第3回 〜観葉植物育成状況の可視化〜 SDP開発とは サービス開発における背景 図1 従来のNTT Comとオンラインサービス時代のサービス開発イメージ 従来のNTT Comのサービスでは、サービスのコアとなる技術要素のみが開発の対象となっており、そのサービスをユーザに届けて対価を得る部分はプロセス整備として扱われ開発の主な対象ではありませんでした。 しかし、近年サービスのオンライン化やセルフマネジメント化が強く推進される中で開発しなければならない範囲は拡大し続けています。 ユーザがサービスのセルフマネジメントを行うためのAPI/GUI、それらを提供するための認証/権限管理機構、ユーザのサービス利用状況の管理/システム化などが必要となってきます。 また、サービスへの申込みや料金計算をオンライン化/自動化したい場合はさらに開発対象の範囲は広がります。 SSS/SDPフレームワーク SDPFポータルで展開するサービスを開発する上で欠かせないSSS/SDPというフレームワークを紹介します。 SSS は「Shared Support Service」、 SDP は「Service Delivery Platform」の略語でNTT Comの用語です。 このフレームワークにおいて、各SDPは サービスのコアになる技術 と それをユーザが利用するためのIF(GUI/API) の開発に集中し、SSSがその土台となる契約の申し込み、契約管理、認証の基盤やユーザ権限管理、料金計算等を一手に担います。 SSS/SDPフレームワークに則り開発を進めることで、それぞれの役割を明確に分担して重複開発を避けられるというアイディアになっています。 SSSや他のSDPとやり取りするための統一的な規格にサービス仕様を合わせることを我々のチームではSDP開発と呼んでいます。 SDP開発によって我々のICGWにおいても、サービス運用やユーザ利便性の面において多くのメリットをもたらしました。 図2 ICGWにおけるSDP開発のBefore/After 図3はSDP開発によって新規に実装されたGUIのサンプルです。 ユーザが設定を投入するためのページだけでなく、利用状況を視覚的に表現したページを提供することによって利便性向上を図っています。 図3 SDP開発による新規提供GUI ICGW-SDP基盤 ICGW全体構成 図4 ICGW全体構成 ICGWは大きく分けて ICGW-core基盤 と ICGW-SDP基盤 の2つからなっています。 ICGW-core基盤はSDP開発前から存在しているもので、デバイスからのデータが流れるデータプレーンやSO APIを提供しています。 ICGW-SDP基盤はSDP開発時に新設されたもので、ユーザが設定を投入するためのGUIやAPI、SSSや他SDPと連携するためのAPIなどを提供しています。 もともとICGWはSDPに対応しない形でサービスリリースしたため、サービスのコアとなる部分はすでに出来ていました。 そのため、ICGW-SDP基盤では特にユーザIFに注力する形での開発となりました。 ICGW-SDP基盤のAPIは裏側でSO APIをコールしており、基本的にユーザのリクエストをそのまま裏へ流し、必要に応じてSDPの規格を満たすようにリクエスト内容を加工できるような設計になっています。 実際にこのような二段構成で運用してみたところ、いくつかのメリットがありましたので列挙しておきます。 ICGW-SDP基盤ではユーザビリティ向上に注力して開発できる ICGW-SDP基盤で対応しない限りユーザに見えることはないので、ICGW-core基盤での新規機能開発がやりやすい サービス仕様やユーザビリティの観点でユーザに見せたくない情報があった場合に、それらをICGW-SDP基盤側でコントロールしやすい CI/CD 我々のチームでは GUI開発 、 API開発 、 CI/CD ともにNTT Comの内製ツールであるQmonusシリーズというものを利用しています。 特にCI/CDを司る Qmonus Value Stream (Qmonus VS)に関しては、開発者ブログに紹介記事がありますので、詳細はそちらをご覧ください。 [DevOpsプラットフォームの取り組み #1] Qmonus Value Streamの紹介 図5は実際のQmonus VSの画面で、新しいバージョンのAPIをステージング環境にデプロイした時の様子です。 Qmonus VSは基本的にGitのブランチマージをトリガーに、いくつかのステップを経てデプロイが実行されます。 ICGWチームでは特にAPIをデプロイする際に、APIの自動試験が実行されるようにステップを構成しており、テストをパスしなければロールバックするという実装になっています。 図5 Qmonus Value StreamにおけるAPIデプロイ時の画面 GUIの自動試験に関してはGithub Actionsを採用しており、同じくブランチマージや任意のタイミングで試験を実施できるようになっています。 また、図6のようにダッシュボードを活用することで、試験中のスナップショットや動画を保存し共有できるような機構も現在計画中です。 図6 GUI自動試験の結果画面 ICGWの可視化 SDP開発に伴い、私の方で開発者/企画者向けにいくつかの可視化ポータルをGCPを活用して作成してみましたので、この場でご紹介します。 APIリクエスト数 図7 APIリクエスト数可視化ポータル こちらはAPIのリクエスト数をレスポンスコードごとに可視化したグラフです。 ICGW-SDP基盤で提供しているIFがどの時間帯にどれくらい利用されているか、500番エラーが多発していないかといった観点のために作成しました。 また、エラーの内容からユーザの間違えやすい傾向なども分析でき、ユーザビリティ向上にも活用できます。 アラート機能と組み合わせることで、リクエスト数の条件に応じてメールやSlack通知を発行することも可能です。 図8が可視化のための全体アーキテクチャです。以下のステップで構成しています。 ユーザのAPIリクエストログをLoggingに転送する レスポンスコードごとに該当のログを抽出できるようにフィルタを追加する ステップ2で抽出したログに対してMonitoring上でカウントしグラフ化する 図8 APIリクエスト数可視化用アーキテクチャ リソース利用状況 図9 リソース利用状況可視化ポータル こちらは全ユーザの設定情報やトラフィック情報をもとに、リソースの利用状況とサービスごとのトラフィック量を時系列的に可視化したポータルです。 どれくらいの数のユーザがどのサービスを使っているかという市場分析用途で作成しました。 これらのグラフは開発者だけでなく企画者にもサービス展開を考える上でご活用いただいています。 図10が可視化のための全体アーキテクチャです。以下のステップで構成しています。 必要なデータを抽出するためのソースコードをStorageに保存する Functionsをビルドする SchedulerからFunctionsが定期的に実行される Funtionsによって抽出された各種データをBigQueryに保存する BigQueryに保存されたデータをData Studioで可視化する 図10 リソース利用状況可視化用アーキテクチャ これらの構成は全てIaaSとしてTerraform管理されており、ソースコードに変更が加わった場合はステップ1/2が自動で実行されるようになっています。 FunctionsからICGW基盤へAPIコールを行う際に、VPCを経由してCloud NATから出ていくことで外部IPを固定でき、ICGW基盤側でのアクセスコントロールが容易になっています。 Data Studioは同じくGoogleが提供している可視化ポータルで、権限があればBigQueryに保存されたデータも容易に読み込むことができ、データの更新も自動で行ってくれます。 また、作成した可視化ポータルは共有リンクを取得することで簡単にチームメンバーへ展開できます。 おわりに ICGW SDP開発のリーダーとして、今回は開発の裏側(?)を紹介しました。 私自身は開発やCI/CDに関してまだまだ勉強途中であり、試行錯誤しながら挑戦しているところなので、より良い手法や開発スタイルがあれば取り込んでいきたいです。 また、今回の可視化ポータルのように、実際に構成を考えて自分の手を動かしてシステムを作っていくことには今後も挑戦できたらと思います。 本記事を通して、ICGWにも興味を持っていただけたら幸いです! IoT Connect Gatewayサービス詳細はこちら
アバター
サマリ Flexible Algorithm による VPN 経路の TE を実現 IOS XR + Junos の Multi-vendor 環境で動作検証に成功 この記事は Multi-AS SR 検証連載の第 6 回です。目次は こちら 概要 イノベーションセンターの田島です。 本記事では Flexible Algorithm を用いた Traffic Engineering について解説します。 Flexible Algorithm で計算された最適経路によって L3VPN の転送が行われる例や、トポロジーが変化しても制約を満たす経路が自動で再計算される例を検証で示します。 これまでの記事でも紹介したように TE を実施する場合において、対象が L3VPN と L2VPN とでは差異が無いため、本記事の内容は L2VPN でも適用可能です。 なお Flexible Algorithm の略称としては Flex-Algo、Flex Algo、Flex-Algorithm などが存在します。本記事中で略記する場合は以下 Flex-Algorithm と表記します。 本記事では Flex-Algorithm についての説明と、ルーターでの動作検証を紹介します。 Flexible Algorithm とは Flexible Algorithm とは IGP での経路計算時に複数の異なるメトリックや制約を付け加え、複数の基準による最適経路を並行して決定し使用するための技術です。 リンクのメトリックとは帯域幅を基にしたコスト、遅延、または TE 用のコストのことです。 制約とは一部のリンクを使用しないような状況のことです。 従来の IGP では、リンクコストを基にし全リンクを使用可能な場合の最適経路のみを計算可能でしたが、 Flex-Algorithm により複数の指標を使い分けることができ、また仮想的にアンダーレイの一部に限定して通信させることができます。 例えば、リンク帯域を基にした最適経路とリンク間遅延を基にした最適経路をそれぞれ Flex-Algorithm で定義し、各 Algorithm を利用するための SR Policy を定義しておきます。 第 4 回の記事 のように BGP color 等によってこれらの SR Policy を選択することで Flex-Algorithm の適用ができます。 このように最適化させたい意図ごとに仮想的にアンダーレイを分割し選択することで、各通信の性質や要求ごとに適切なアンダーレイおよび最適なパスでの通信が実現されます。 Flex-Algorithm により区別されたトポロジーは128から255まで番号をつけることができ、 メトリックの種類やトポロジー制約の記述などとともに定義されます。 各リンクに属性を割り当て、特定の属性のリンクを含むか、または含まないかで制約が記述されます。 この属性を color と表記し、本記事で後述する例でも RED や BLUE のようにリンクを区別します。 なお 第 4 回の記事 に BGP color の説明がありましたが、別物です。 Flex-Algorithm の color は、各属性を1ビットと見立てて割り当てられていれば1に対応するビットマップとして実装され Extended Administrative Group (EAG) や Administrative Group (AG) の値として保存されます。 これらの情報からなる定義は Flexible Algorithm Definition (FAD) と呼ばれ、 IGP により広告されます。 Flex-Algorithm は IETF Network Working Group での Internet-Draft 1 段階の技術であり、2022年8月時点では完全に標準化されている方式ではありません。 しかし、各社のルーター製品やオープンソース製品などにおいて実装が始まっています。 FAD の広告には EAG や AG などの Type Length Value (TLV) を使用しますが、使用する TLV や Sub-TLV を相互に認識可能で無ければ相互接続ができません。 実際に Extended Administrative Group や Application-Specific Link Attribute の解釈が違うことで意図した動作にならないことがありましたが、 IOS XR 7.4.1 と Junos 21.3 以降の組み合わせでは相互運用できることを確かめました。 Flex-Algorithm の動作例 Flex-Algorithm の動作をトポロジー例を用いて説明します。 図の中央上部にアンダーレイトポロジーを記載しています。 PE 2台、 P 2台の各リンクに対して RED と BLUE のラベル、 IGP メトリックと TE メトリックを設定しています。 このアンダーレイで 2 つの Flex-Algorithm を作成します。 Algorithm 128 : RED リンクは使用禁止。 IGP メトリックに従って最適経路を算出する。 Algorithm 129 : BLUE リンクのみを使用する。 TE メトリックに従って最適経路を算出する。 それぞれ図の左下と右下のような、制約が反映され指定のメトリックがついたトポロジーとして仮想的に認識されます。 この状態で転送経路としてどちらかの Algorithm を選択すると、それぞれの仮想的なトポロジー内での最適経路に従って転送されます。 Algorithm を明示的や自動的に選択するために SRGB をそれぞれ分けます。 例えばこの例だと Algorithm 128 側を SRGB 17000 から、 129 側を 18000 からというように設定します。 これにより Algorithm を指定した宛先の指定ができます。 リンク障害などで元のアンダーレイトポロジーから変化した場合でも、それぞれの Algorithm ごとに再計算されます。 これは自動計算されるバックアップパスも Algorithm の制約に従うことを意味します。 しかし気を付けるべき点もあります。制約を入れた後のトポロジーで単一障害点となるリンクが存在するような非冗長な場合において、そのリンクが障害状態になると当該 Algorithm はネットワークが分断され不通になってしまいます。 実際のアンダーレイが問題なく冗長化されていて通信継続させたい場合は、当該 Algorithm の使用を指定する SR Policy の定義などでフォールバック先を指定しておく必要があります。 Flex-Algorithm の適用検証 実際に Flex-Algorithm を用いて TE を実装します。 概要部分での説明に用いた次の図のトポロジーを使用し Algorithm 128 を設定します。 なお、次節で障害時の動作を確かめます。 このアンダーレイで RED リンクを使用せず IGP メトリックに従う Algorithm 128 を定義します。 あらかじめ顧客 A の BGP 経路には BGP color 100 をつけておき、定義した Algorithm 128 を顧客 A の VPN 経路に使用します。 転送経路は次のように表せ、これを実装します。 なお 129 を実装しない理由としては FAD の解釈にメーカー差があり想定の経路へは TE されないためです。 2023/09/19 追記:本記事公開後の OS のバージョンアップによって FAD の相互運用ができるようになりました。 第 15 回 の記事を参照ください。 検証に用いたバージョンは下記の通りです。 PE1、P1: Cisco IOS XR 7.4.1 PE2、P2: Junos OS 21.3R1.9 第 4 回の記事 の通りに L3VPN および BGP color の SR Policy を作成します。実施内容は下記の通りで、詳しくはリンク先記事を参照ください。 VRF 100 と 200 による L3VPN 作成 BGP color の付与と広告 BGP color による SR Policy の適用 ここからは次のステップで設定し確認します。 リンクに対する制約やメトリックの定義 Flex-Algorithm 128 の定義 ラベル学習の確認 SR Policy の設定と確認 traceroute による VPN の通信経路確認 1. リンクに対する制約やメトリックの定義 各ルーターにてインターフェースに対し Flex-Algorithm の color と IGP および TE のメトリックを指定します。 color はビットマップでフラグ管理されるため、各色で何ビット目を用いるかの定義がそれぞれのルーターに必要です。 今回は行きと帰りの経路を対象にするため 1 つのリンクに対して両端のルーターで同じ設定にします。 PE に関しては VPN の next-hop として Loopback インターフェースが使用されるため、特定の color のリンクのみを使用するといった制約を意味する include-any や include-all を用いる際は Loopback に当該 color を忘れずに付ける必要があります。 BLUE は最終的に本検証で使用しませんが、設定方法の参考として同時に設定します。 PE1 (IOS XR) P1 向け Gi0/0/0/0 を RED かつ BLUE にします PE であるため Loopback0 に BLUE をつけます router isis 1 affinity-map RED bit-position 1 affinity-map BLUE bit-position 2 ! interface Loopback0 affinity flex-algo BLUE ! interface GigabitEthernet0/0/0/0 affinity flex-algo RED BLUE address-family ipv4 unicast metric 10 ! ! interface GigabitEthernet0/0/0/1 address-family ipv4 unicast metric 10 ! ! ! segment-routing traffic-eng interface GigabitEthernet0/0/0/0 metric 10 ! interface GigabitEthernet0/0/0/1 metric 10 ! ! ! P1 (IOS XR) PE1 向け Gi0/0/0/0 を RED かつ BLUE にします PE2 向け Gi0/0/0/1 と P2 向け Gi0/0/0/2 を BLUE にします router isis 1 affinity-map RED bit-position 1 affinity-map BLUE bit-position 2 ! interface GigabitEthernet0/0/0/0 affinity flex-algo RED BLUE address-family ipv4 unicast metric 10 ! ! interface GigabitEthernet0/0/0/1 affinity flex-algo BLUE address-family ipv4 unicast metric 10 ! ! interface GigabitEthernet0/0/0/2 affinity flex-algo BLUE address-family ipv4 unicast metric 10 ! ! ! segment-routing traffic-eng interface GigabitEthernet0/0/0/0 metric 10 ! interface GigabitEthernet0/0/0/1 metric 100 ! interface GigabitEthernet0/0/0/2 metric 10 ! ! ! PE2 (Junos) P1 向け ge-0/0/0 と P2 向け ge-0/0/1 を BLUE にします PE であるため lo0 に BLUE をつけます set protocols isis interface ge-0/0/0.0 level 2 te-metric 100 set protocols isis interface ge-0/0/0.0 level 2 application-specific attribute-group Algo129 admin-group BLUE set protocols isis interface ge-0/0/0.0 level 2 application-specific attribute-group Algo129 application flex-algorithm set protocols isis interface ge-0/0/0.0 level 2 metric 10 set protocols isis interface ge-0/0/1.0 level 2 te-metric 10 set protocols isis interface ge-0/0/1.0 level 2 application-specific attribute-group Algo129 admin-group BLUE set protocols isis interface ge-0/0/1.0 level 2 application-specific attribute-group Algo129 application flex-algorithm set protocols isis interface ge-0/0/1.0 level 2 metric 10 set protocols isis interface lo0.0 level 2 application-specific attribute-group Algo129 admin-group BLUE set protocols isis interface lo0.0 level 2 application-specific attribute-group Algo129 application flex-algorithm set protocols mpls admin-groups RED 1 set protocols mpls admin-groups BLUE 2 set protocols mpls interface ge-0/0/0.0 admin-group BLUE set protocols mpls interface ge-0/0/1.0 admin-group BLUE set protocols mpls interface lo0.0 admin-group BLUE P2 (Junos) P1 向け ge-0/0/1 と PE2 向け ge-0/0/2 を BLUE にします set protocols isis interface ge-0/0/0.0 level 2 te-metric 10 set protocols isis interface ge-0/0/0.0 level 2 metric 10 set protocols isis interface ge-0/0/1.0 level 2 te-metric 10 set protocols isis interface ge-0/0/1.0 level 2 application-specific attribute-group Algo129 admin-group BLUE set protocols isis interface ge-0/0/1.0 level 2 application-specific attribute-group Algo129 application flex-algorithm set protocols isis interface ge-0/0/1.0 level 2 metric 100 set protocols isis interface ge-0/0/2.0 level 2 te-metric 10 set protocols isis interface ge-0/0/2.0 level 2 application-specific attribute-group Algo129 admin-group BLUE set protocols isis interface ge-0/0/2.0 level 2 application-specific attribute-group Algo129 application flex-algorithm set protocols isis interface ge-0/0/2.0 level 2 metric 10 set protocols mpls admin-groups RED 1 set protocols mpls admin-groups BLUE 2 set protocols mpls interface ge-0/0/1.0 admin-group BLUE set protocols mpls interface ge-0/0/2.0 admin-group BLUE 2. Flex-Algorithm 128 の定義 Algorithm 128 を定義し広告します。 下記では各ルーターごとに掲示していますが、同じものが設定されます。 Algorithm ごとの転送に用いるため、各 Node-SID も Algorithm ごとに付与します。 ここでは Algorithm 128 は 17000 番台へと空間を分けています。 下記設定では比較をわかりやすくするために、 Algorithm がついていない元の Node-SID の設定も残しています。 PE1 (IOS XR) router isis 1 flex-algo 128 advertise-definition affinity exclude-any RED ! interface Loopback0 address-family ipv4 unicast prefix-sid index 1 prefix-sid algorithm 128 index 1001 ! ! ! P1 (IOS XR) router isis 1 flex-algo 128 advertise-definition affinity exclude-any RED ! interface Loopback0 address-family ipv4 unicast prefix-sid index 2 prefix-sid algorithm 128 index 1002 ! ! ! PE2 (Junos) Junos において RSVP などのシグナリングが別に動作していないときはドキュメントに従い set protocols isis traffic-engineering advertisement always が必要です。 set policy-options policy-statement Flex-Algo-SID term 1 from route-filter 10.255.0.3/32 exact set policy-options policy-statement Flex-Algo-SID term 1 then prefix-segment algorithm 128 index 1003 set policy-options policy-statement Flex-Algo-SID term 1 then prefix-segment algorithm 128 node-segment set routing-options flex-algorithm 128 definition metric-type igp-metric set routing-options flex-algorithm 128 definition spf set routing-options flex-algorithm 128 definition admin-group exclude RED set protocols isis source-packet-routing node-segment ipv4-index 3 set protocols isis source-packet-routing flex-algorithm 128 set protocols isis source-packet-routing strict-asla-based-flex-algorithm set protocols isis traffic-engineering advertisement always set protocols isis export Flex-Algo-SID P2 (Junos) set policy-options policy-statement Flex-Algo-SID term 1 from route-filter 10.255.0.4/32 exact set policy-options policy-statement Flex-Algo-SID term 1 then prefix-segment algorithm 128 index 1004 set policy-options policy-statement Flex-Algo-SID term 1 then prefix-segment algorithm 128 node-segment set routing-options flex-algorithm 128 definition metric-type igp-metric set routing-options flex-algorithm 128 definition spf set routing-options flex-algorithm 128 definition admin-group exclude RED set protocols isis source-packet-routing node-segment ipv4-index 4 set protocols isis source-packet-routing flex-algorithm 128 set protocols isis source-packet-routing strict-asla-based-flex-algorithm set protocols isis traffic-engineering advertisement always set protocols isis export Flex-Algo-SID 3. ラベル学習の確認 上記設定が完了すると FAD の広告がされるため、各ルーターで各 Algorithm の情報を見ることができます。 FAD の内容と、ラベルが学習されているのかを確認します。 PE1 (IOS XR) PE1 自身の広告内容を確認します。 FAD の定義内容や隣接である P1 へのリンクの color が見えます。 なおビットマップのため RED は 1 << 1 = 0x2 となり、 BLUE は 1 << 2 = 0x4 となり、 RED かつ BLUE は 0x2 & 0x4 = 0x6 と表現されます。 RP/0/RP0/CPU0:PE1#show isis database verbose Mon Aug 15 10:50:02.431 JST IS-IS 1 (Level-2) Link State Database LSPID LSP Seq Num LSP Checksum LSP Holdtime/Rcvd ATT/P/OL PE1.00-00 * 0x000007ed 0xe7e5 1067 /* 0/0/0 Area Address: 49.0000 NLPID: 0xcc IP Address: 10.255.0.1 Metric: 10 IP-Extended 10.0.0.0/30 Prefix Attribute Flags: X:0 R:0 N:0 E:0 A:0 Metric: 10 IP-Extended 10.0.1.0/30 Prefix Attribute Flags: X:0 R:0 N:0 E:0 A:0 Metric: 10 IP-Extended 10.255.0.1/32 Prefix-SID Index: 1, Algorithm:0, R:0 N:1 P:0 E:0 V:0 L:0 Prefix-SID Index: 1001, Algorithm:128, R:0 N:1 P:0 E:0 V:0 L:0 Prefix-SID Index: 2001, Algorithm:129, R:0 N:1 P:0 E:0 V:0 L:0 Prefix Attribute Flags: X:0 R:0 N:1 E:0 A:0 Source Router ID: 10.255.0.1 Hostname: PE1 Router ID: 10.255.0.1 Router Cap: 10.255.0.1 D:0 S:0 Segment Routing: I:1 V:0, SRGB Base: 16000 Range: 8000 SR Local Block: Base: 15000 Range: 1000 Node Maximum SID Depth: Label Imposition: 10 SR Algorithm: Algorithm: 0 Algorithm: 1 Algorithm: 128 Algorithm: 129 Flex-Algo Definition: Algorithm: 128 Metric-Type: 0 Alg-type: 0 Priority: 128 Flex-Algo Exclude-Any Ext Admin Group: 0x00000002 Flex-Algo Definition: Algorithm: 129 Metric-Type: 0 Alg-type: 0 Priority: 128 Flex-Algo Include-Any Ext Admin Group: 0x00000004 Metric: 10 IS-Extended P1.00 (snip) Application Specific Link Attributes: L flag: 0, SA-Length: 1, UDA-Length: 1 Standard Applications: 0x10 FLEX-ALGO User Defined Applications: 0x10 Ext Admin Group: 0x00000006 Affinity: 0x00000006 ADJ-SID: F:0 B:0 V:1 L:1 S:0 P:0 weight:0 Adjacency-sid:24003 (snip) 元々の Node-SID だけではなく、各 Algorithm で設定した Node-SID も学習されています。 RP/0/RP0/CPU0:PE1#show mpls forwarding Mon Aug 15 11:07:47.194 JST Local Outgoing Prefix Outgoing Next Hop Bytes Label Label or ID Interface Switched ------ ----------- ------------------ ------------ --------------- ------------ (snip) 16003 16003 SR Pfx (idx 3) Gi0/0/0/0 10.0.0.2 5416 (snip) 17003 17003 SR Pfx (idx 1003) Gi0/0/0/1 10.0.1.2 0 (snip) PE2 (Junos) FAD を参照するために show isis database extensive 以外にも Flex-Algorithm のコマンドがあります。 user@PE2> show isis spring flex-algorithm flex-algorithm-id 128 Flex Algo: 128 Level: 2, Color: 100, Participating, FAD supported Winner: P1, Metric: 0, Calc: 0, Prio: 128, Excl: 0x2, FAD supported Exclude: 0x2 RED Spf Version: 632 Participation toggles: 1 Last Route add: 0 Last Route rem: 0 Last Route chg: 0 Last Route fail: 0 Last Route def: 0 Total Route add: 16 Total Route rem: 8 Total Route chg: 0 Total Route fail: 0 Total Route def: 0 Topo refresh count: 2 PE1, Metric: 0, Calc: 0, Prio: 128, Excl: 0x2, FAD supported P2, Metric: 0, Calc: 0, Prio: 0, Excl: 0x2, FAD supported PE2, Metric: 0, Calc: 0, Prio: 0, Excl: 0x2, FAD supported Full SPFs: 631, Partial SPFs: 0 IS-IS Flex Algo 128 level 2 SPF log: Start time Elapsed (secs) Count Reason Sun Aug 14 11:03:06 0.000056 1 Periodic SPF (snip) ラベルも学習されています。 user@PE2> show route table mpls.0 (snip) 16001 *[L-ISIS/14] 5d 16:04:39, metric 30 to 10.0.2.1 via ge-0/0/0.0, Swap 16001 > to 10.0.3.2 via ge-0/0/1.0, Swap 16001 (snip) 17001 *[L-ISIS/14] 5d 14:55:16, metric 30 > to 10.0.3.2 via ge-0/0/1.0, Swap 17001 (snip) 4. SR Policy の設定と確認 On Demand Nexthop (ODN) を使用して特定の BGP color の経路に使用する Algorithm を指定します。 PE1 (IOS XR) segment-routing traffic-eng on-demand color 100 dynamic metric type igp ! sid-algorithm 128 ! ! ! ! SR Policy が up 状態になり、宛先も PE2 の Algorithm 128 の SID (17003) が選択されています。 RP/0/RP0/CPU0:PE1#show segment-routing traffic-eng policy color 100 Mon Aug 15 13:27:34.701 JST SR-TE policy database --------------------- Color: 100, End-point: 10.255.0.3 Name: srte_c_100_ep_10.255.0.3 Status: Admin: up Operational: up for 4d12h (since Aug 10 14:31:37.908) Candidate-paths: Preference: 200 (BGP ODN) (active) Requested BSID: dynamic Constraints: Prefix-SID Algorithm: 128 Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) Metric Type: IGP, Path Accumulated Metric: 30 17003 [Prefix-SID: 10.255.0.3, Algorithm: 128] Preference: 100 (BGP ODN) Requested BSID: dynamic PCC info: Symbolic name: bgp_c_100_ep_10.255.0.3_discr_100 PLSP-ID: 3 Constraints: Prefix-SID Algorithm: 128 Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (pce) (invalid) Last error: No path Metric Type: NONE, Path Accumulated Metric: 0 Attributes: Binding SID: 24012 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no 顧客 A の BGP 経路も上記で設定した SR Policy (24012) を使用するようになっています。 RP/0/RP0/CPU0:PE1#show route vrf 100 192.168.1.0/24 detail Mon Aug 15 13:34:37.770 JST Routing entry for 192.168.1.0/24 Known via "bgp 65000", distance 200, metric 0, type internal Installed Aug 10 14:31:37.916 for 4d13h Routing Descriptor Blocks 10.255.0.3, from 10.255.0.3 Nexthop in Vrf: "default", Table: "default", IPv4 Unicast, Table Id: 0xe0000000 Route metric is 0 Label: 0x10 (16) Tunnel ID: None Binding Label: 0x5dcc (24012) Extended communities count: 0 Source RD attributes: 0x0000:65000:100 NHID:0x0(Ref:0) Route version is 0x5 (5) No local label IP Precedence: Not Set QoS Group ID: Not Set Flow-tag: Not Set Fwd-class: Not Set Route Priority: RIB_PRIORITY_RECURSIVE (12) SVD Type RIB_SVD_TYPE_REMOTE Download Priority 3, Download Version 22 No advertising protos. PE2 (Junos) set routing-options flex-algorithm 128 color 100 こちらも PE1 の Algorithm 128 の SID (17001) が選択されています。 user@PE2> show route table inetcolor.0 inetcolor.0: 6 destinations, 6 routes (6 active, 0 holddown, 0 hidden) + = Active Route, - = Last Active, * = Both 10.255.0.1-100<c>/64 *[L-ISIS/14] 5d 17:18:36, metric 30 > to 10.0.3.2 via ge-0/0/1.0, Push 17001 (snip) 顧客 A の BGP 経路も 17001 SID として積むようになっています。 user@PE2> show route table 100.inet.0 192.168.0.0/24 extensive 100.inet.0: 3 destinations, 3 routes (3 active, 0 holddown, 0 hidden) 192.168.0.0/24 (1 entry, 1 announced) TSI: KRT in-kernel 192.168.0.0/24 -> {indirect(1048579)} *BGP Preference: 170/-101 Route Distinguisher: 65000:100 Next hop type: Indirect, Next hop index: 0 Address: 0x704fafc Next-hop reference count: 3 Source: 10.255.0.1 Next hop type: Router, Next hop index: 654 Next hop: 10.0.3.2 via ge-0/0/1.0, selected Label operation: Push 24004, Push 17001(top) Label TTL action: prop-ttl, prop-ttl(top) Load balance label: Label 24004: None; Label 17001: None; Label element ptr: 0x780fb50 Label parent element ptr: 0x782f1d0 Label element references: 1 Label element child references: 0 Label element lsp id: 0 Session Id: 0x142 Protocol next hop: 10.255.0.1-100<c> Label operation: Push 24004 Label TTL action: prop-ttl Load balance label: Label 24004: None; Indirect next hop: 0x7199204 1048579 INH Session ID: 0x0 State: <Secondary Active Int Ext ProtectionCand> Local AS: 65000 Peer AS: 65000 Age: 4d 10:32:58 Metric: 0 Metric2: 30 Validation State: unverified ORR Generation-ID: 0 Task: BGP_65000.10.255.0.1 Announcement bits (1): 0-KRT AS path: ? Communities: target:65000:100 color:0:100 Import Accepted VPN Label: 24004 Localpref: 100 Router ID: 10.255.0.1 Primary Routing Table: bgp.l3vpn.0 Thread: junos-main Indirect next hops: 1 Protocol next hop: 10.255.0.1-100<c> Metric: 30 Label operation: Push 24004 Label TTL action: prop-ttl Load balance label: Label 24004: None; Indirect next hop: 0x7199204 1048579 INH Session ID: 0x0 Indirect path forwarding next hops: 1 Next hop type: Router Next hop: 10.0.3.2 via ge-0/0/1.0 Session Id: 0x142 10.255.0.1-100<c>/64 Originating RIB: inetcolor.0 Metric: 30 Node path count: 1 Forwarding nexthops: 1 Next hop type: Router Next hop: 10.0.3.2 via ge-0/0/1.0 Session Id: 0x0 5. traceroute による VPN の通信経路確認 PE1 -> PE2 PE1 -> P2 -> P1 -> PE2 と通過していることが観測できます。 RP/0/RP0/CPU0:PE1#traceroute 192.168.1.254 vrf 100 Mon Aug 15 13:39:24.020 JST Type escape sequence to abort. Tracing the route to 192.168.1.254 1 10.0.1.2 [MPLS: Labels 17003/16 Exp 0] 15 msec 13 msec 3 msec 2 10.0.4.1 [MPLS: Labels 17003/16 Exp 0] 27 msec 8 msec 28 msec 3 192.168.1.254 3 msec 9 msec 2 msec PE2 -> PE1 逆順の PE2 -> P1 -> P2 -> PE1 と通過していることが観測できます。 user@PE2> traceroute 192.168.0.254 routing-instance 100 traceroute to 192.168.0.254 (192.168.0.254), 30 hops max, 52 byte packets 1 10.0.2.1 (10.0.2.1) 2.871 ms 13.558 ms 2.522 ms MPLS Label=17001 CoS=0 TTL=1 S=0 MPLS Label=24004 CoS=0 TTL=1 S=1 2 10.0.4.2 (10.0.4.2) 2.522 ms 2.372 ms 2.534 ms MPLS Label=17001 CoS=0 TTL=1 S=0 MPLS Label=24004 CoS=0 TTL=2 S=1 3 10.0.1.1 (10.0.1.1) 10.075 ms * 4.672 ms リンク障害時の Flex-Algorithm 検証 前節で設定したネットワークにて1つのリンクを故意に閉塞し、障害時においても Algorithm 内でパスが切り替わる動作を確かめます。 落とすリンクは P1 と P2 の間です。 当該のリンクを落とした後も Algorithm 128 は有効なため、 Algorithm 128 内での最短パスに切り替わります。 切り替え前の P2 および PE2 のテーブル どちらも P1 を向いています。 P2 user@P2> show route table mpls.0 label 17003 17003 *[L-ISIS/14] 5d 20:37:15, metric 20 > to 10.0.4.1 via ge-0/0/2.0, Swap 17003 PE2 user@PE2> show route table mpls.0 label 17001 17001 *[L-ISIS/14] 01:52:13, metric 40 > to 10.0.2.1 via ge-0/0/0.0, Swap 17001 切り替え後の P2 および PE2 のテーブル どちらもメトリックが増え、直接それぞれ転送するようになります。 P2 user@P2> show route table mpls.0 label 17003 17003 *[L-ISIS/14] 00:00:29, metric 100 > to 10.0.3.1 via ge-0/0/1.0, Pop PE2 user@PE2> show route table mpls.0 label 17001 17001 *[L-ISIS/14] 00:00:22, metric 120 > to 10.0.3.2 via ge-0/0/1.0, Swap 17001 traceroute による VPN の通信経路確認 PE1 -> PE2 リンク障害時の図の通り PE1 -> P2 -> PE2 と通過していることが観測できます。 RP/0/RP0/CPU0:PE1#traceroute 192.168.1.254 vrf 100 Mon Aug 15 15:45:51.232 JST Type escape sequence to abort. Tracing the route to 192.168.1.254 1 10.0.1.2 [MPLS: Labels 17003/16 Exp 0] 8 msec 12 msec 2 msec 2 192.168.1.254 10 msec 2 msec 8 msec PE2 -> PE1 逆順の PE2 -> P2 -> PE1 と通過していることが観測できます。 user@PE2> traceroute 192.168.0.254 routing-instance 100 traceroute to 192.168.0.254 (192.168.0.254), 30 hops max, 52 byte packets 1 10.0.3.2 (10.0.3.2) 2.428 ms 1.969 ms 1.463 ms MPLS Label=17001 CoS=0 TTL=1 S=0 MPLS Label=24004 CoS=0 TTL=1 S=1 2 10.0.1.1 (10.0.1.1) 12.766 ms * 9.734 ms まとめ 本記事ではアンダーレイの分割や複数指標での最適経路計算ができる Flexible Algorithm ついて解説しました。 Multi-vendor での実装とリンク障害時の動作検証を紹介しました。 Flexible Algorithm はまだ draft 段階のため、機能追加が盛んで相互運用性も限定的ですが、ネットワーク網の意図を表現する方法として VPN の TE にも使用可能と考えられます。 次回の記事では、Flex-Algorithm のメトリックとしても使える、遅延測定値を基にした Delay-based TE を紹介予定です。 (2022/09/05 追記) 公開しました: [Multi-AS Segment Routing 検証連載 #7] Delay-Based TE https://datatracker.ietf.org/doc/html/draft-ietf-lsr-flex-algo ↩
アバター
はじめに こんにちは、イノベーションセンターの福田です。 今回、開発環境改善の取り組みとして GitHub Actions の self-hosted runners を AWS 上に構築しました。 この構築で得られた知見について共有します。 概要 GitHub Actions は GitHub で CI/CD を手軽に実現する機能です。 GitHub が提供している環境を利用して、 CI/CD のジョブを実行できます 1 。 一方で、ハードウェア等をカスタマイズできないため、例えば容量が大きくより速度の早いストレージを利用したい場合や、より多くのメモリを利用したい場合に対応ができません。 そこで、GitHub Actions には self-hosted runners という機能があり、自身の環境で GitHub Actions の CI/CD ジョブを走らせる環境を用意できます。 今回はより柔軟に利用しやすく、保守しやすいように AWS Cloud Development Kit (以下CDK)を用いて AWS 上に self-hosted runners 環境を構築したので、その構築手順等について解説していきたいと思います。 アーキテクチャ 今回構築するシステムのアーキテクチャについて解説します。 構築するインフラの構成図は次の通りです。 図の中で点線によってグループ化されていますが、本システムは大きく次の2つの部分からなっています。 Webhook イベントの受信部分 self-hosted runners 環境提供部分 GitHub では GitHub Actions の CI/CD ジョブが走り始めたときに Webhook イベントを飛ばすことができ、これを利用して CI/CD ジョブの開始と同時にその CI/CD ジョブを走らせる self-hosted runners を起動するというシステムになっています。 Webhook イベントの受信 まず、 self-hosted runners で走らせたい CI/CD ジョブが開始されたのかを知る必要があります。 GitHub には workflow_job というイベントが Webhook イベントとして出ており、このイベントのペイロードに以下の情報が含まれています。 ジョブの実行環境の指定 GitHub-hosted runners での実行か、 self-hosted runners での実行かも指定されています。 ジョブのステータス ジョブの開始が要求されたのか、現在ジョブが走っているのか、ジョブが終了したのかのステータスがわかります。 この workflow_job イベントを Webhook イベントとして受信することで self-hosted runners で走らせたい CI/CD ジョブが開始されたのかを知ることができます。 したがって、最初に workflow_job イベントを受信する Webhook の受信先を AWS に用意します。 AWS には API Gateway というサービスがあり、このサービスを使って容易に Web API を作成できます。 なので、 workflow_job イベントの受信部分に関してはこの API Gateway を使って構築します。 しかし、 workflow_job は GitHub-hosted runners で走らせたい CI/CD ジョブが開始されたときにも発行されるイベントです。 そのため、 API Gateway で作成した Web API でこの workflow_job イベントを受信し self-hosted runners を起動するという単純な実装にしてしまうと self-hosted runners で走らせたいジョブ数よりも大くの self-hosted runners が起動し、予定よりも多くの課金がなされてしまいます。 これを避けるため、作成した Web API が webhook イベントを受信したら self-hosted runners で走らせたい CI/CD ジョブの開始が要求されたイベントだけを抽出し、それ以外のイベントは無視するような仕組みを導入します。 今回使用する API Gateway は Lambda 関数 の HTTP エンポイントを提供できるので、この機能を利用し、作成した Web API のバックエンドで Lambda 関数によるイベントフィルタリングの機能を追加します。 しかし、この Lambda 関数の HTTP エンドポイントの機能には Lambda 関数の最大実行時間が29秒までであるという制約が存在します。 そのまま self-hosted runners を動かそうとするとこの制約にひっかかってしまい、 self-hosted runners が起動できなくなる可能性があります。 これを回避するために、 Lambda 関数はフィルターを行った後に self-hosted runners を起動するようなことはせず、一旦 SQS にフィルターしたイベントを送信します。 こうすれば SQS からあとの処理は独立して行えるため、先述した制限を回避できます。 2 。 以上をまとめると、 Webhook の受信部分は次のようなインフラストラクチャーを構築することになります。 self-hosted runners の提供 ここからは受信した Webhook イベントを元に、適切な self-hosted runners を走らせる VM (以下、 self-hosted runners ノード) を立ち上げるためのインフラ構成について解説します。 SQS には Webhook イベントが送信されているので、まずはその SQS からメッセージを受信する部分を構築します。 SQS がメッセージを受信したとき、Lambda 関数へメッセージの受信イベントを通知できます 3 。 この機能を利用して、 SQS から Webhook イベントを受信次第 self-hosted runners ノードとなる VM である EC2 インスタンス を起動する Lambda 関数を用意します。 この際、 EC2 インスタンスを EC2 インスタンスの起動を行う API を通して起動することになるのですが、ここで Launch Template という EC2 インスタンスの起動パラメータのテンプレートを利用して起動するようにします。 Launch Template は CDK で簡単に用意することができる ため、こちらの機能を通して起動する方が Lambda 関数に EC2 インスタンスの起動パラメータを全て渡すようなやり方よりも楽に保守・管理・運用できます 4 。 このように、 SQS から Webhook イベントを受信してから Lambda 関数を起動し、その Lambda 関数は self-hosted runners ノードを起動することで self-hosted runners を必要な分だけ提供できます。 起動した self-hosted runners ノードは CI/CD ジョブを実行し、その CI/CD ジョブが終了次第シャットダウンするようにします。 EC2 インスタンスにはシャットダウン終了後に自動で VM の削除するように設定でき、この設定もしておきます。 こうすることでインスタンスやそのストレージへの課金額を抑えられると同時に、前のジョブの情報がインスタンス内に残らないようにできます。 つまり、毎回クリーンな環境でインスタンスを起動でき、各ジョブに依存関係が発生するのを防ぐことができます。 最後に、 self-hosted runners を起動する際には以下の2つの処理を self-hosted runners の起動前に行う必要があるので、これらを EC2 インスタンスの 起動時にスクリプトを走らせる機能 を使っておこないます。 最新版の self hosted runners のダウンロード self-hosted runners を登録 このとき、シェルクリプトでやるには複雑すぎることがあったり、やりとりされるパケットの量を抑えたりするために、いくつかの処理を Lambda 関数を通して行うようにします。 これらをまとめると、以下のような構成図を構築することになります。 図中には S3 バケットが出現していますが、こちらは 1. のダウンロードしてきた self-hosted runners をキャッシュするために利用しています。 CDK によるインフラ構築 先述したシステムを Web コンソールや CLI だけで構築し、適切に保守・運用していくのは難しいため CDK を利用します。 self-hosted runners 環境 最初に self-hosted runners ノードを起動する仕組みを構築します。 self-hosted runners ノード自体は Public な場所に置いておく必要はなく、 GitHub との接続ができればよいため Private な サブネット へと設置するようにし、 Public なサブネットに NAT ゲートウェイ を設置して Private サブネットの通信をそこへ流すようにします。 まずは self-hosted runners ノード起動時に走らせるスクリプト内で呼ばれる補助 Lambda 関数を用意します。 self-hosted runners を提供するプログラムをダウンロードする関数 ダウンロードするバイナリーにバージョン番号がついているため、先に最新版のバージョン番号を GitHub API から取得しておく必要があり、スクリプトの複雑度や制御難易度を上げてしまうので肩代わりさせる 関数を呼び出せば最新版が常に手に入り、テストも用意になる 走らせる self-hosted runners を GitHub に登録するためのトークンをつくる関数 この部分は JWT をつくったりするなど多少複雑なロジックを実装する必要があり、 bash スクリプトでやるよりも Lambda 関数に処理を移譲してしまった方が良さそうです 実は 1. の場合は Lambda 関数の制限として 6MB までのレスポンスしか返せません 。 self-hosted runners はこの制限である 6MB を越えています 。 そのため、Lambda 関数としては self-hosted runners をそのまま返すことはせずに、 S3 バケット に一旦ダウンロードしてきたものを配置し、その配置先をレスポンスに載せます。 ダウンロードは VPC に S3 のエンドポイントを設置して、 Lambda 関数のレスポンスから配置先をよみとって S3 バケットからダウンロードしてきます。 こうすることで、副次的に NAT ゲートウェイ を通るパケットの量を減らすことができるので、 NAT ゲートウェイの料金を抑える効果もあります。 さて、この2つの関数を CDK に載せていきます。 なお、ここでは用意する関数はすべて Go 言語 で記載しています。 また、 Go 言語の module 機能を利用するので go v1.11 以上が必要です。 さらに、 CDK では Python や Java 等様々な言語が使えますが、ここでは TypeScript を用いて CDK コードを記述していきます。 最初に 1. の関数を載せます。 やりたいこととしては以下の4つになります。 ダウンロードしたい self-hosted runners のバージョンを取得 先程取得したバージョンを指定して self-hosted runners をダウンロード ダウンロードしたものを S3 バケットに設置 設置先を関数の返り値として返す これを動かすコードは以下のようになります。 package main import ( "context" "encoding/json" "fmt" "io/ioutil" "net/http" "os" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" ) type Response struct { Bucket string `json:"bucket"` Key string `json:"key"` } type PackageMetadata struct { TagName string `json:"tag_name"` } func Fetch(url string ) ([] byte , error ) { resp, err := http.Get(url) if err != nil { return nil , err } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) } func HandleRequest(ctx context.Context) (Response, error ) { // self-hosted runners のキャッシュ先 S3 バケット名は // 環境変数として設定する bucket, has := os.LookupEnv( "BUCKET" ) if !has { return Response{}, fmt.Errorf( "No such environement: BUCKET" ) } cfg, err := config.LoadDefaultConfig(ctx) if err != nil { return Response{}, err } client := s3.NewFromConfig(cfg) // 1. ダウンロードしたい self-hosted runners のバージョンを取得 // // ここでは最新版をダウンロード b, err := Fetch( "https://api.github.com/repos/actions/runner/releases/latest" ) if err != nil { return Response{}, err } m := &PackageMetadata{} if err := json.Unmarshal(b, m); err != nil { return Response{}, err } // 先頭に `v` がプリフィックスとしてついてるので、 // そこだけ無視してバージョンを取得 ver := m.TagName[ 1 :] // 2. 先程取得したバージョンを指定して self-hosted runners をダウンロード b, err = Fetch( fmt.Sprintf( "https://github.com/actions/runner/releases/download/v%s/actions-runner-linux-x64-%s.tar.gz" , ver, ver) ) if err != nil { return Response{}, err } // 3. self-hosted runners を S3 に設置する param := &s3.PutObjectInput{ Bucket: aws.String(bucket), // 決め打ちで runner.tar.gz というキーで // self-hosted runners のバイナリーを保存しておく Key: aws.String( "runner.tar.gz" ), Body: b, } if _, err := client.PutObject(ctx, param); err != nil { return Response{}, err } // 4. 設置先を関数の返却値として返す return Response{Bucket: bucket, Key: "runner.tar.gz" }, nil } func main() { lambda.Start(HandleRequest) } Lambda は Docker コンテナを動かせる ので、 そのための Dockerfile を書きます。 FROM --platform=$TARGETPLATFORM golang:1 as build WORKDIR /usr/src/app ARG TARGETOS ARG TARGETARCH COPY go.mod go.sum ./ RUN go mod download && go mod tidy COPY . . RUN env CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -v -o /main ./... FROM --platform=$TARGETPLATFORM public.ecr.aws/lambda/provided:al2 COPY --from=build /main ${LAMBDA_RUNTIME_DIR}/bootstrap CMD [ "main" ] 同じように 2. の関数も用意します。 注意点として、 事前に GitHub Apps として self-hosted runners を登録する Lambda 関数を登録する必要があります。 GitHub Apps として登録する際には以下2点を設定しておかなければならないことに注意してください。 self-hosted runners を登録する Lambda 関数が self-hosted runners まわりの操作を Read/Write できるよう権限を付与しておく GitHub Organizations の全てのリポジトリで動作するようにさせる場合、 Repository Access を All Repositories に設定しておく この GitHub Apps への登録ができたら、起動させる self-hosted runners を GitHub へ登録する関数を書いていきます。 package main import ( "context" "fmt" "os" "strconv" "github.com/aws/aws-lambda-go/lambda" "github.com/bradleyfalzon/ghinstallation/v2" "github.com/google/go-github/v45/github" ) func InstallationId(org string ) ( int64 , error ) { itr, err := ghinstallation.NewAppsTransport( https.DefaultTransport, appId, privateKey, ) if err != nil { return 0 , err } client := github.NewCient(&http.Client{Transport: itr}) listOpt := &github.ListOptions{} for { is, resp, err := client.Apps.ListInstallations(ctx, listOpt) if err != nil { return 0 , err } // GitHub Apps の全てのインストール先から、 // 指定された Organization にインストールされているものを見つけて // そのインストールにつけられた ID を取得する。 for _, inst := range insts { if inst.GetAccount().GetLogin() == org { return inst.GetID(), nil } } if resp.NextPage == 0 { break } listOpt.Page = resp.NextPage } return 0 , fmt.Errorf( "cannot find installation id on %s" , org) } func HandleRequest(ctx context.Context) ( map [ string ] string , error ) { org, has := os.LookupEnv( "ORGANIZATION" ) if !has { return "" , fmt.Errorf( "No such environment: ORGANIZATION" ) } appIdStr, has := os.LookupEnv( "GITHUB_APPS_APP_ID" ) if !has { return "" , fmt.Errorf( "No such environment: GITHUB_APPS_APP_ID" ) } appId, err = strconv.ParseInt(appIdStr, 10 , 64 ) if err != nil { return "" , fmt.Errorf( "GITHUB_APPS_APP_ID is not integer: %s" , appIdStr) } privateKey, has := os.LookupEnv( "GITHUB_APPS_APP_PRIVATE_KEY" ) if !has { return "" , fmt.Errorf( "No such environment: GITHUB_APPS_APP_PRIVATE_KEY" ) } instId, err := InstallationId(org) if err != nil { return "" , err } // 2. self-hosted runners を登録する際に必要となるトークンの発行 itr, err := ghinstallation.New(http.DefaultTransport, appId, instId, privateKey) if err != nil { return "" , err } client := github.NewCient(&http.Client{Transport: itr}) resp, _, err := client.Actions.CreateOrganizationRegistrationToken(ctx, org) if err != nil { return "" , err } return resp.GetToken(), nil } func main() { lambda.Start(HandleRequest) } 先述した2つの関数を CDK として載せます。 import * as cdk from "aws-cdk-lib" ; import * as { Construct } from "constructs" ; import * as path from "path" ; // self-hosted runners をダウンロードする Lambda 関数 export class RunnerDownloader extends cdk.aws_lambda.DockerImageFunction { constructor( scope: Construct , id: string ) { super( scope , id , { code: cdk.aws_lambda.DockerImageCode.fromImageAsset ( // Lambda 関数のコードの設置先はこのソースファイルと同じ階層にある // `handler/runner-downloader` に path.join ( __dirname , "handler" , "runner-downloader" ), ), timeout: cdk.Duration.minutes ( 5 ), memorySize: 1024 , } ); const storage = new cdk.aws_s3.Bucket ( this , "Storage" , { lifecycleRules: [ // 7日間だけはキャッシュしておくが、 // 7日超えたら最新のものを取得 { enabled: true , expiration: cdk.Duration.days ( 7 ), } ] , // キャッシュとしてしか利用しないので、スタックの削除時には // このバケット内のオブジェクトを全て削除する autoDeleteObjercts: true , removalPolicy: cdk.RemovalPolicy.DESTROY , } ); storage.grantRead ( this ); storage.grantPut ( this ); this .addEnvironment ( "BUCKET" , storage.bucketName ); } } export type RegistrationTokenGeneratorProps = { githubAppsPrivateKey: string ; githubAppsAppId: string ; } ; // self-hosted runners を登録するためのトークンを生成する Lambda 関数 export class RegistrationTokenGenerator extends cdk.aws_lambda.DockerImageFunction { constructor( scope: Construct , id: string , props: RegistrationTokenGeneratorProps ) { super( scope , id , { code: cdk.aws_lambda.DockerImageCode.fromImageAsset ( // Lambda 関数のコードの設置先はこのソースファイルと同じ階層にある // `handler/registration-token-generator` に path.join ( __dirname , "handler" , "registration-token-generator" ) ), timeout: cdk.Duration.minutes ( 5 ), environment: { GITHUB_APPS_APP_ID: props.githubAppsAppId , GITHUB_APPS_PRIVATE_KEY: props.githubAppsPrivateKey , } , memorySize: 1024 , } ); } } 用意できたら self-hosted runners を起動するスクリプトを組みます。 #!/usr/bin/env bash function setup() { local -r \ runner_downloader="$1" \ registration_token_generator="$2" \ organization="$3" \ runner_directory="$4" \ mkdir "$runner_directory" && cd "$runner_directory" || return 1 # self-hosted runners を取得する local -r runner_json=/tmp/runner.json aws lambda invoke \ --function-name "$runner_downloader" \ --payload "{}" \ --cli-binary-format raw-in-base64-out \ "$runner_json" local -r runner_archive=/tmp/runner.tar.gz aws s3api get-object \ --bucket "$(jq -r .bucket "$runner_json")" \ --key "$(jq -r .key "$runner_json")" \ "$runner_archive" tar zxf "$runner_archive" # self-hosted runners の登録用トークン発行 local -r registration_token_json=/tmp/registration-token.json aws lambda invoke \ --function-name "$registration_token_generator" \ --payload "{}" \ --cli-binary-format raw-in-base64-out \ "$registration_token_json" local registration_token registration_token="$(jq -r .token "$registration_token_json")" # runner の初期設定を行う ./config.sh \ --url "https://github.com/$organization" \ --disable-update \ --ephemeral \ --unattended } function main() { local -r \ runner_downloader="$1" \ registration_token_generator="$2" \ organization="$3" # self-hosted runners を走らせるためのユーザーを作成 useradd -m -g users -G sudo -s /bin/bash runner # self-hosted runners を起動する準備を行う local runner_directory=~runner/runner su \ - runner \ -c "$(declare -f setup); setup \"$runner_downloader\" \"$registration_token_generator\" \"$organization\" \$runner_directory\"" cd "$runner_directory" && ./bin/installdependencies.sh # self-hosted runners を起動 # self-hosted runners がタスクを走り終えたら poweroff して VM を終了させる su - runner -c "cd '$runner_directory' && ./run.sh" \ && systemctl poweroff } main "$@" あとはこのスクリプトを利用して self-hosted runners ノードとなる EC2 インスタンスのための Launch Template を作成します。 import * as cdk from "aws-cdk-lib" ; import { Construct } from "constructs" ; import * as path from "path" ; // 先程用意した補助関数をインポートする。 import { RunnerDownloader } from "./runner-downloader" ; import { RegistrationTokenGenerator } from "./registration-token-generator" ; export type LaunchTemplateProps = { runnerDownloader: RunnerDownloader ; registrationTokenGenerator: RegistrationTokenGenerator ; organization: string ; } ; export class LaunchTemplate extends cdk.Resource { public readonly launchTemplate: cdk.aws_ec2.LaunchTemplate ; constructor( scope: Construct , id: string , props: LaunchTemplateProps ) { super( scope , id ); const role = new cdk.aws_iam.Role ( this , "Role" , { assumedBy: new cdk.aws_iam.ServicePrincipal ( "ec2.amazonaws.com" ), managedPolicies: [ cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName ( "AmazonSSMManagedInstanceCore" ), ] , } ); const asset = new cdk.aws_s3_assets.Asset ( this , "UserData" , { // 先程のスクリプトを start.bash という名前で保存しておく。 path: path.join ( __dirname , "start.bash" ), } ); asset.grantRead ( role ); const userData = cdk.aws_ec2.UserData.forLinux ( { shebang: "#!/usr/bin/env bash" , } ); const filePath = this .userData.addS3DownloadCommand ( { bucket: asset.bucket , bucketKey: asset.s3ObjectKey , } ); userData.addExecuteFileCommand ( { filePath , arguments : [ props.runnerDownloader.functionArn , props.registrationTokenGenerator.funcitonArn , organization , ] .join ( " " ), } ); this .launchTemplate new cdk.aws_ec2.LaunchTemplate ( this , "LaunchTemplate" , { role , instanceType: cdk.aws_ec2.InstanceType. of( cdk.aws_ec2.InstanceClass.T3 , cdk.aws_ec2.InstanceSize.SMALL , ), // ここには GitHub Actions で利用するイメージを指定する。 // ただし、先程のスクリプト内で awscli v2 と jq を利用しているので、 // これらを含めた上で、 GitHub Actions でサポートされている // タイプの OS を指定する必要がある。 // // 参考: https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners#supported-architectures-and-operating-systems-for-self-hosted-runners machineImage: GithubActionsMachineImage , userDate , ebsOptimized: true , // シャットダウン時にはインスタンスを terminate させる。 instanceInitiatedShutdownBehavior: cdk.aws_ec2.InstanceInitiatedShutdownBehavor.TERMINATE , blockDevices: [ { deviceName: "/dev/sda1" , volume: cdk.aws_ec2.BlockDeviceVolume.ebs ( 100 , { volumeType: cdk.aws_ec2.EvsDeviceVolumeType.GP3 , deleteOnTermination: true , encrypted: true , } ) } ] } ); } } 次にこの Launch template からインスタンスを起動する Lambda 関数を書きます。 まずは関数のコードを用意します(この関数も先程までの関数と同じで Lambda コンテナイメージとして提供します)。 package main import ( "context" "fmt" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2/types" ) // あとでこの関数を SQS に接続し、 SQS からイベントをうけとったら起動するようにするため、 // ここでは events.SQSEvent を受け取るようにしています。 func HandleRequest(ctx context.Context, req events.SQSEvent) error { ltId, has := os.LookupEnv( "LAUNCHTEMPLATE_ID" ); if !has { return fmt.Errorf( "No such environment: LAUNCHTEMPLATE_ID" ); } ltVer, has := os.LookupEnv( "LAUNCHTEMPLATE_VERSION" ); if !has { return fmt.Errorf( "No such environment: LAUNCHTEMPLATE_VERSION" ) } snId, has := os.LookupEnv( "SUBNET_ID" ); if !has { return fmt.Errorf( "No such environment: SUBNET_ID" ); } cfg, err := config.LoadDefaultConfig(ctx) if err != nil { return err } client := ec2.NewFromConfig(cfg) for range req.Records { param := &ec2.RunInstancesInput{ LaunchTemplate: &types.LaunchTemplateSpecification{ LaunchTemplateId: aws.String(ltId), Version: aws.String(ltVer), }, SubnetId: aws.String(snId), MaxCount: aws.Int32( 1 ), MinCount: aws.Int32( 1 ), } resp, err := client.RunInstances(ctx, param) if err != nil { return err } } return nil } func main() { lambda.Start(HandleRequest) } この関数の CDK コードを用意します。 // 先程の LaunchTemplate クラスをインポートする。 import { LaunchTemplate } from "./launch-template" ; export type InstanceLauncherProps = { launchTemplate: LaunchTemplate ; subnet: cdk.aws_ec2.ISubnet ; } ; // self-hosted runners を登録するためのトークンを生成する Lambda 関数 export class InstanceLauncher extends cdk.aws_lambda.DockerImageFunction { constructor( scope: Construct , id: string , props: InstanceLauncherProps ) { super( scope , id , { code: cdk.aws_lambda.DockerImageCode.fromImageAsset ( // Lambda 関数のコードの設置先はこのソースファイルと同じ階層にある // `handler/registration-token-generator` に path.join ( __dirname , "handler" , "instance-launcher" ) ), timeout: cdk.Duration.minutes ( 5 ), environment: { LAUNCHTEMPLATE_ID: props.launchTemplate.launchTemplateId , LAUNCHTEMPLATE_VERSION: props.launchTemplate.latestVersionNumber , SUBNET_ID: props.subnet.subnetId , } , memorySize: 1024 , } ); this .addToRolePolicy ( new cdk.aws_iam.PolicyStatement ( { resources: [ "*" ] , // EC2 インスタンスを立ち上げるには `ec2:RunInstances` が必要。 // また、 立ち上げた EC2 インスタンスにロールを付与するために `iam:PassRole` も必要で、 // さらにタグを Launch Template を通してつけたりするためには CreateTags が必要となる。 actions: [ "ec2:RunInstances" , "iam:PassRole" , "ec2:CreateTags" ] } ) ); } } 最後にこれらをまとめたコンポーネントを作成します。 import * as cdk from "aws-cdk-lib" ; import { Construct } from "constructs" ; import { InstanceLauncher } from "./instance-launcher" ; import { RunnerDownloader } from "./runner-downloader" ; import { RegistrationTokenGenerator } from "./registration-token-generator" ; import { LaunchTemplate } from "./LaunchTemplate" ; export type SelfHostedRunnerProps = { githubAppsAppId: string ; githubAppsPrivateKey: string ; organization: string ; subnet: cdk.aws_ec2.ISubnet ; } ; export class SelfHostedRunner extends cdk.Resource { public readonly instanceLauncher: InstanceLauncher ; constructor( scope: Construct , id: string , props: SelfHostedRunnerProps ) { super( scope , id ); const runnerDownloader = new RunnerDownloader ( this , "RunnerDownloader" ); const registrationTokenGenerator = new RegistrationTokenGenerator ( this , "RegistrationTokenGenerator" , { githubAppsAppId: props.githubAppsAppId , githubAppsPrivateKey: props.githubAppsPrivateKey , } ); const launchTemplate = new LaunchTemplate ( this , "LaunchTemplate" , { runnerDownloader , registrationTokenGenerator , organization: props.organization , } ); this .instanceLauncher = new InstanceLauncher ( this , "InstanceLauncher" , { launchTemplate , subnet: props.subnet , } ); } } Webhook self-hosted runners ノードを起動する部分の作成ができたので、今度は Webhook を受信する部分を作成してきます。 最初に、 Webhook イベントを送信する SQS と、先程用意した self-hosted runners ノードを起動する Lambda 関数がその SQS のメッセージ受信したら起動するようにする設定します。 import * as cdk from "aws-cdk-lib" ; import { Construct } from "constructs" ; // instance を立ち上げる Lambda 関数をインポート import { InstanceLauncher } from "./instance-launcher" ; export type InstanceLaunchQueueProps = { // プロパティで self-hosted runners ノードを起動する Lambda 関数を渡してもらう instanceLauncher: InstanceLauncher ; } export class InstanchLaunchQueue extends cdk.aws_sqs.Queue { constructor( scope: Construct , id: string , props: InstanceLaunchQueueProps ) { super( scope , id , { visibilityTimeout: cdk.Duration.minutes ( 5 ) } ); // SQS のイベントを受信して EC2 インスタンスを立ち上げる Lambda 関数を呼ぶ props.intanceLauncher.addEventSource ( new cdk.aws_lambda_event_sourecs.SqsEventSource ( this ) ); } } 次に Webhook の受信部分となる API Gateway による Web API の作成と、その裏で動くイベントのフィルタリングを行ったあとに SQS へ Webhook イベントを送信する Lambda 関数を実装します。 イベントのフィルタリング方法ですが、 先述したとおり、 workflow_job イベントを利用します。 この workflow_job イベントのペイロードには workflow というオブジェクトが含まれており、この中に status というプロパティがあります。 この status プロパティは CI/CD ジョブのステータスを表しています。 ステータスとしては次の3つが用意されています。 queued : ジョブが開始したものの、まだ runner が割り当てられていない状態 in_progress : ジョブに runner が割り当てられて現在ジョブが走っている状態 completed : ジョブが完了したことを表す状態 このうち、 queued が今回必要になるイベントであるため、これ以外のものをまずははじくことが必要です。 さらに、 workflow_job イベントのペイロード中にある workflow オブジェクトには labels という文字配列プロパティがあり、 self-hosted runners で走らせる CI/CD ジョブが開始した場合はこの配列中に self-hosted というラベルが入っています。 なので、 self-hosted runners で走るジョブだけを抽出する際にはこの labels プロパティの要素も見てあげる必要があります。 これらを元に必要となる Webhook イベントのみを抽出し、 self-hosted runners ノードを起動するところまでを作成します 5 。 まず、イベントフィルタリングをしたあとに SQS へメッセージを送信する Lambda 関数の実装は次の通りです。 package main import ( "context" "fmt" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/google/go-github/v45/github" ) // あとで API Gateway からこの関数を呼び出す func HandleRequest(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error ) { qUrl, has := os.LookupEnv( "QUEUE_URL" ) if !has { resp := events.APIGatewayProxyResponse{ StatusCode: 500 , Body: "No such environment: QUEUE_URL" , } return resp, fmt.Errorf( "No such environment: QUEUE_URL" ) } // イベントのフィルタリング var ev github.WorkflowJobEvent if err := json.Unmarshal(req.Body, &ev); err != nil { // workflow_job イベント以外は全部無視 resp := events.APIGatewayProxyResponse{ StatusCode: 200 , Body: "ignore event" , } return resp, nil } if stat := ev.GetWorkflowJob().GetStatus(); stat != "queued" { // queued なもの以外は全部無視 resp := events.APIGatewayProxyResponse{ StatusCode: 200 , Body: "ignore event" , } return resp, nil } isShr := false for _, label := range ev.GetWorkflowJob().Labels { if label == "self-hosted" { isShr = true break } } if !isShr { // queued なもの以外は全部無視 resp := events.APIGatewayProxyResponse{ StatusCode: 200 , Body: "ignore event" , } return resp, nil } // Queue にメッセージを送信 cfg, err := config.LoadDefaultConfig(ctx) if err != nil { resp := events.APIGatewayProxyResponse{ StatusCode: 500 , Body: fmt.Sprintf( "%v" , err), } return resp, err } client := sqs.NewFromConfig(cfg) param := &sqs.SendMessageInput{ QueueUrl: aws.String(qUrl), // Queue からイベントがでればそのまま self-hosted runners が立ち上がるため、 // メッセージは何でもよい。そのため、ここでは `{}` をメッセージとして送信している。 MessageBody: aws.String( "{}" ), } if _, err = client.SendMessage(ctx, param); err != nil { resp := events.APIGatewayProxyResponse{ StatusCode: 500 , Body: fmt.Sprintf( "%v" , err), } return resp, err } resp := events.APIGatewayProxyResponse{ StatusCode: 200 , Body: "OK" , } return resp, nil } func main() { lambda.Start(HandleRequest) } この関数をプロビジョニングするための CDK コードは次の通りです。 import * as cdk from "aws-cdk-lib" ; import { Construct } from "constructs" ; import * as path from "path" ; import { InstanceLaunchQueue } from "./instance-launch-queue" ; export type EventPublisherProps = { queue: InstanceLaunchQueue ; } ; export class EventPublisher extends cdk.aws_lambda.DockerImageFunction { constructor( scope: Construct , id: string , props: EventPublisherProps ) { super( scope , id , { code: cdk.aws_lambda.DockerImageCode.fromImageAsset ( // Lambda 関数のコードの設置先はこのソースファイルと同じ階層にある // `handler/event-publisher` に path.join ( __dirname , "handler" , "event-publisher" ) ), timeout: cdk.Duration.minutes ( 5 ), environment: { QUEUE_URL: props.queue.queueUrl , } , memorySize: 1024 , } ); this .addToRolePolicy ( new cdk.aws_iam.PolicyStatement ( { resources: [ "*" ] , actions: [ "sqs:SendMessage" ] } ) ); } } 最後に、 Webhook の受け口となる API Gateway を設置し、用意した Lambda 関数を呼び出すように設定すれば作成完了です。 import * as cdk from "aws-cdk-lib" ; import { Construct } from "constructs" ; // 作成したものをインポート import { EventPublisher } from "./event-publisher" ; import { InstanceLaunchQueue } from "./instance-launch-queue" ; import { InstanceLauncher } from "./instance-launcher" ; export type WebhookProps = { instanceLauncher: InstanceLauncher ; } ; export class Webhook extends cdk.Resource { public readonly gateway: cdk.aws_apigateway.LambdaRestApi ; constructor( scope: Construct , id: string , props: InstanceLauncher ) { super( scope , id ); const queue = new InstanceLaunchQueue ( this , "Queue" , { instanceLauncher: props.instanceLauncher , } ); const eventPublisher = new EventPublisher ( this , "EventPublisher" , { queue , } ); this .gateway = new cdk.aws_apigateway.LambdaRestApi ( this , "Webhook" , { handler: eventPublisher } ); } } あとは GitHub 側で Webhook を作成し、ここでデプロイされる API Gateway の URL を Webhook として登録すれば完了です。 まとめ ここまで self-hosted runners を AWS 上に CDK を用いて構築する方法について解説しました。 多少複雑な機構となってはいますが、やりたいこととしては Webhook から CI/CD ジョブの開始イベントがきたら self-hosted runners を立ち上げるだけです。 ここでは解説していませんが、多少拡張していけば arm64 アーキテクチャな self-hosted runners 環境を作成したり、 Windows 環境を用意して実行したりできます。 この環境をベースとして皆さんでより良い開発環境を作成していただければと思います。 https://docs.github.com/ja/actions/using-github-hosted-runners/about-github-hosted-runners ↩ self-hosted runners の立ち上げは Webhook イベントの受信とは別の操作になりますし、最悪その後の処理で EC2 インスタンスが立ち上がらなくても GitHub Actions のジョブは72時間で失敗としてとりあつかわれます。さらにジョブは再度流せるので、 self-hosted runners 環境を再度立ち上げてジョブを走らせることができればいいため、切り離すことによる問題はそこまで大きくはないと思います。 ↩ https://docs.aws.amazon.com/ja_jp/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-lambda-function-trigger.html ↩ Auto Scaling Group を使えば同じようにインスタンスの起動をより楽に行えますが、起動したインスタンスの管理が煩雑になります。少し試してみればわかりますが、インスタンス数を増やすのはとても簡単な一方、インスタンス数を減らすのはとても難しいです。たとえば、大抵の CI/CD ジョブはすぐに終了することが多いため、無駄なコストをかけないように土日には self-hosted runners 環境を提供する EC2 インスタンスの数を0にするとします。しかし、 ML トレーニングするインスタンス等時間のかかる CI/CD ジョブが金曜夜に投入され、土曜にジョブが割り込んだとき、上記ルールに則りインスタンスが終了させられ、ジョブが中断させられてしまいます。これをちゃんとどのインスタンスが走っているのかを把握するのは難しいため、適切なジョブ管理をする仕組みを導入しなければなりません。なので、 Auto Scaling Group を使わずにジョブの要求に応じて Launch Template から起動するようにしておき、ジョブが走った後はそのまま EC2 インスタンスを終了するようにすれば複雑なジョブの管理は必要なくなります。 ↩ 実は GitHub には Organization で共有して利用できる self-hosted runners の他に、リポジトリー毎にも self-hosted runners を登録できます。 workflow_job イベントではこれらの区別がつかないため、リポジトリー毎に登録している self-hosted runners を利用したいのか、 Organization で共有して利用できる self-hosted runners を起動したいのかを区別することは難しいです。簡単な解決策としては各 self-hosted runners に ラベル を登録できるので、これを利用してそれぞれを区別するようにすれば良いです。ただし、今回示す例ではそのようなリポジトリー毎に登録されている self-hosted runners はないものとして進めます。 ↩
アバター
はじめに DevOpsプラットフォームの取り組みを紹介する5回目の記事です。 Qmonus Value Stream 開発チームの杉野です。 連載第5回では、Qmonus Value StreamでCI/CD機能を実現するための要素技術として用いている、OSSのTektonについて紹介します。 これまでの記事をまだ見ていないという方は、Qmonus Value StreamというプラットフォームがどのようにTektonを利用しているかを 過去の記事 で述べていますので、覗いてみてください。 また、本記事ではKubernetes(以下、k8s)に関する知識がある前提で記述していますので、ご了承ください。 Tekton とは Tektonは一言でいうと、CI/CDシステムを作成するためのKubernetes Nativeなオープンソースフレームワークです。さらに噛み砕いて表現すると、k8s上で動作してCI/CDの機能を実現するソフトウェアです。 Tektonは、基盤となるTekton Pipelinesと呼ばれるコンポーネントと、Tektonをエコシステムにするための複数のサポートコンポーネントで構成されます。現在は次のようなコンポーネントが存在しています。 Tekton Pipelines : パイプラインを構築するためのビルディングブロックを提供するTektonの基盤コンポーネント Tekton Triggers : パイプラインをイベントトリガーで実行する機能を提供 Tekton CLI : パイプラインを管理するコマンドラインインタフェースを提供 Tekton Dashboard : パイプラインの確認や実行が可能なWeb UIを提供 Tekton Catalog : コミュニティが提供するビルディングブロックのリポジトリ Tekton Hub : Tekton CatalogのWeb UIを提供 Tekton Operator : Tektonのコンポーネントを管理する機能を提供 Tekton Results : パイプラインの実行履歴のストレージ機能を提供 Tekton Chains : パイプラインでサプライチェーンセキュリティの機能を提供 上記コンポーネントの中でもPipelines、Triggers、CLI、Dashboardは主要コンポーネントと呼ばれます。本記事では、Tekton Pipelinesに焦点をあてて紹介します。 Tekton Pipelines コンセプト TektonにはStep、Task、Pipelineと呼ばれる概念があります。 Step : Tektonでパイプラインを実行する際の一番小さな処理の単位です。例えば、ソースコードを取得する・コンパイルする・テストするといった処理に相当します。 Task : 順番に実行されるStepの集まりです。シーケンシャルに実行される一連の処理に相当します。 Pipeline : Taskの集まりです。Taskの順序関係の設定および直列・並列実行の制御、Taskの実行条件の設定などが可能です。 これらの概念の関係性を図示すると、以下の通り表現されます。 上記の3つの概念を用いてCI/CDパイプラインを記述し実行することで、k8sのワークロードリソースが生成されて、タスク処理が実行されます。前述の図にk8sリソースとの関係性を記述してみます。 上図にあるように、StepはContainerに、TaskはPodに対して1対1に対応しています。k8sの目線で考えると、Taskの実行はPodを起動すること、Stepを順番に処理することはPod内のコンテナを順番に処理することに相当します。データ共有に関しては、Step間ならばコンテナ間で共有、Task間ならPod 間でデータ共有することのように言い換えられます。 TektonにはTask/Pipelineの実行の概念であるTaskRun/PipelineRunが存在します。TaskRunによってTaskの内容をもとにPodが起動され、Stepであるコンテナの処理が順次実行されます。PipelineRunはPipelineの内容を元に各TaskをTaskRunを利用して実行します。 Tekton Pipelinesは、上記で説明した Task 、 Pipeline 、 TaskRun 、 PipelineRun をk8sのカスタムリソースとして提供しています。ユーザはそれらのリソースを組み合わせてパイプラインを構築します。 パイプラインの作成と実行例 ここでは、具体的なカスタムリソースの定義と実行例を紹介します。 以下の実行環境で動作を確認しています。 Component Version Kubernetes v1.24.1 Tekton Pipelines v0.38.0 Tekton CLI v0.25.0 Task の定義と実行 サンプルとして、次の仕様を満たすTaskのカスタムリソースを定義します。 パラメータ名 message として文字列を受け取る。受け取らない場合は、"Hello, Tekton Task"をデフォルト値とする。 step-1、step-2のstepが順番に実行され、受け取ったパラメータをechoする。 apiVersion : tekton.dev/v1beta1 kind : Task metadata : name : echo namespace : sample-tekton spec : params : - name : message type : string description : echo this message default : Hello, Tekton Task steps : - name : step-1 image : ubuntu command : - echo args : - $(params.message) - name : step-2 image : ubuntu command : - echo args : - $(params.message) サンプル中の以下の部分では、実行の際にTaskが受け取るパラメータを定義しています。パラメータは配列形式で複数定義でき、パラメータ名、型、デフォルト値を指定できます。パラメータの型として、stringかarrayが指定可能です。 params : - name : message type : string description : echo this message default : Hello, Tekton Task Task内でStepなどを定義する際に $(params.<パラメータ名>) の形式で記述することで、Taskが実行される際に受け取ったパラメータの値で置換できます。 次に、Taskが実行するStepについてです。Stepは次のように配列として定義できます。コンセプトで説明した通り、Stepの1つ1つがコンテナに相当します。 steps : - name : step-1 image : ubuntu command : - echo args : - $(params.message) steps配列に定義される各stepの定義は、Podで設定するコンテナの指定に似ています。今回の場合だと、 step-1 という名前のstep(コンテナ)は ubuntu コンテナイメージを使い、 echo コマンドを実行し、 $(params.message) を置換した値が引数として渡されるという意味になります。また、後ほどの例にでてきますが、commandの代わりにscriptフィールドを指定することで、コンテナで実行するスクリプトを記述するような方法もあります。 TaskRunリソースを作成することで、上記で定義したTaskを実行します。以下にTaskRunのサンプルを示します。 apiVersion : tekton.dev/v1beta1 kind : TaskRun metadata : generateName : echo- spec : params : - name : message value : Hello taskRef : name : echo spec.params には、Taskのパラメータ名と渡したい値のペアを配列で指定します。 spec.taskRef には、このTaskRunで実行するTaskを指定します。TaskRunリソースではTaskリソースを参照する他にインラインで定義することも可能ですが、ここでは割愛します。 metadata.generateName が書かれたマニフェストを作成すると、生成されたリソースの名前にランダムなサフィックスが付与されます。これにより、生成されたTaskRunリソースの名前が重複しなくなるため、同じTaskRunマニフェストを用いて複数個のTaskRunリソースを作成し、何回でもTaskを実行できるようになります。 これらの設定をk8sクラスタに適用しCLIで確認すると、以下のような結果を得ることができます。ここで tkn はTekton CLIをインストールすることで利用可能になるバイナリです。 > tkn taskrun logs -n sample-tekton -f [step-1] Hello [step-2] Hello 以上のように、Taskリソースを定義することで、複数のコンテナを組み合わせて任意の処理を実行するCI/CDタスクを組み上げることができます。 Pipeline の定義と実行 サンプルとして、次の仕様を満たすPipelineのカスタムリソースを定義します。 前述のecho Taskに異なるパラメータを与えて、2つのTaskを実行する 2つのTaskは並列に実行する apiVersion : tekton.dev/v1beta1 kind : Pipeline metadata : name : hello namespace : sample-tekton spec : params : - name : task1-message type : string default : Hello, Task1 - name : task2-message type : string default : Hello, Task2 tasks : - name : task1 taskRef : name : echo params : - name : message value : $(params.task1-message) - name : task2 taskRef : name : echo params : - name : message value : $(params.task2-message) params ではTaskと同様に、実行の際にPipelineが受け取るパラメータを定義します。 Pipelineが実行するTaskは、次のように配列として定義します。 tasks : - name : task1 taskRef : name : echo params : - name : message value : $(params.task1-message) PipelineでのTaskの指定の仕方は、前述のTaskRunリソースでの指定の仕方と同じです。Taskを直接実行する際にTaskRunを生成するのと同様に、PipelineがTaskを実行する際にもTaskRunが生成されるためです。ここで指定したTask定義は、PipelineがTaskRunを生成する際に使用されます。列挙したTaskは、後述のrunAfterが宣言されていない限り、すべて並列に実行されます。 PipelineRunリソースを作成することで、上記で定義したPipelineを実行します。以下にPipelineRunのサンプルを示します。 apiVersion : tekton.dev/v1beta1 kind : PipelineRun metadata : generateName : hello- namespace : sample-tekton spec : pipelineRef : name : hello params : - name : task1-message value : Hello, Task1 - name : task2-message value : Hello, Task2 spec.pipelineRef には、このPipelineRunで実行するPipelineを指定します。 spec.params には、Pipelineのパラメータ名と渡したい値のペアを配列で指定します。 これらの設定をk8sクラスタに適用しCLIで確認すると、以下のような結果を得ることができます。 > tkn pipelinerun logs -n sample-tekton -f [task2 : step-1] Hello, Task2 [task2 : step-2] Hello, Task2 [task1 : step-1] Hello, Task1 [task1 : step-2] Hello, Task1 以上のように、Pipelineリソースを定義することで、複数のTaskを組み合わせてより複合的なCI/CDタスクを組み上げることができます。 Task の実行順序制御 コンセプトの項目でも触れましたが、PipelineはTaskの実行順序を制御できます。 あるTaskを別のTaskの後で実行したいときには、Pipelineリソースで runAfter を指定します。前述のPipelineにおいてtask2をtask1の後で実行したい場合は、パイプラインで次のように指定します。 tasks : - name : task1 taskRef : name : echo params : - name : message value : $(params.task1-message) - name : task2 taskRef : name : echo params : - name : message value : $(params.task2-message) runAfter : # task1 との依存関係を定義 - task1 上記の指定にあるように、 runAfter は配列の形式で指定できます。そのため、複数のTaskとの依存関係を記述でき、 ファンインとファンアウトの両方を構成できます。 runAfter をうまく活用することで、並列実行と直列実行を組み合わせて複雑なPipelineを組むことが可能です。 データの共有 複数の処理で構成されたパイプラインを実行した際、処理間でデータの共有を必要とする場合があります。Tektonでは、そのようなケースで利用できる機能として results と workspaces が提供されています。 results : resultsは、Pipelineを介してTask間でTask実行結果を共有できる機能です。例えば、ビルドしたコンテナイメージの Tagやハッシュ値を後続のTaskで利用するなどの使い方があります。 workspaces : workspacesは、Task実行時に1つ以上のボリュームをマウント可能とする機能です。Step間のみの共有ならばemptyDir、Task間共有が必要ならばPersistentVolumeと使い分けが可能です。例えば、ソースコードをcloneしてきたあと、後続の処理で使うといった使い方があります。 上記機能のうち、resultsの例を紹介します。 サンプルのパイプラインを次の仕様で作成します。 パラメータとして与えられた文字列を連結するTaskと文字列をechoするTaskの2つが実行される 文字列を連結するTaskの実行結果をresultsとして保存し、echo Taskでresultsをecho する 文字列を連結するTaskを次のように定義します。 apiVersion : tekton.dev/v1beta1 kind : Task metadata : name : concat namespace : sample-tekton spec : results : - name : concat-msg description : A string that is a concatenation of two strings params : - name : msg1 type : string - name : msg2 type : string steps : - name : concat image : ubuntu script : | #!/usr/bin/env bash echo -n $(params.msg1) $(params.msg2) > $(results.concat-msg.path) resultsを出力するTaskは、Task内で以下のように spec.results を定義する必要があります。 results : - name : concat-msg description : A string that is a concatenation of two strings その上で、Step内の実装に、resultsを書き出す処理を追加します。resultsとして出力したい文字列を $(results.<results名>.path) に対して書き出すことで、書き出した値をTaskのresultとして扱えるようになります。 2つのTaskを順番に実行するPipelineは、以下のように記述されます。 apiVersion : tekton.dev/v1beta1 kind : Pipeline metadata : name : concat-msg namespace : sample-tekton spec : params : - name : msg1 type : string - name : msg2 type : string tasks : - name : concat taskRef : name : concat params : - name : msg1 value : $(params.msg1) - name : msg2 value : $(params.msg2) - name : echo taskRef : name : echo params : - name : message value : $(tasks.concat.results.concat-msg) ポイントは、後続のecho Taskのパラメータに指定している $(tasks.concat.results.concat-msg) の記述です。Pipeline上で $(tasks.<task名>.results.<result名>) のフォーマットで記述することで、task名で指定したTaskが出力したresultsの値で置換できます。 以下のPipelineRunリソースを作成して、Pipelineを実行します。 apiVersion : tekton.dev/v1beta1 kind : PipelineRun metadata : generateName : concat-msg- namespace : sample-tekton spec : pipelineRef : name : concat-msg params : - name : msg1 value : Hello - name : msg2 value : World 実行した後の確認結果は次のようになりました。連結された文字列が適切にecho Taskに渡ったのが確認できます。 > tkn pipelinerun logs -n sample-tekton -f [echo : step-1] Hello World [echo : step-2] Hello World ここで1点補足です。 runAfter を指定しない場合、Taskは並列実行されると前述しました。その場合、適切にresultsの値がとれるのか?と疑問を覚えると思います。この点に関しては、task間でresultsの参照関係がある場合には実行順序制御が自動で行われるため、明示的にrunAfterを記述する必要はありません。ただ、明示的に記述した方がわかりやすいとの意見もありますので、チームメンバーと方針を決めるのがよいかと思います。 Tekton Pipelinesの機能紹介はここまでとなります。本記事では紹介していない機能もありますので、興味を持たれた方はぜひ試してみてください。 何故、Tekton を選んだか 最後に、Qmonus Value Streamの要素技術としてTektonを選択した理由について紹介します。 まず、CI/CD機能そのものを提供するOSSやSaaSを選択しなかった理由について説明します。当初、私達がDevOpsの取り組みの中でCI/CDパイプラインの作成や改善を続けていくにあたり、SaaSやOSSの検証も行いました。どのサービス・ソフトウェアもよくできていて学ぶことはとても多いのですが、私達のユースケースを実現するためには課題もありました。 例えば、以下のような課題がありました。 構築したいCI/CDのサイクルが複雑で、シンプルな機能だけでは構築が難しい。例えば、複数のコンポーネントを複数の異なるバージョンで組み合わせて動作させたいケースなど。 k8sだけでなく、各パブリッククラウド特有の挙動を考慮したロジックを組み立てる必要がある。 このような背景があり、ある程度決まった枠組みが作られているサービス・ソフトウェアよりも、ワークフローのエンジンとなるような技術の方が私達に適しているのではないか、という結論に至りました。 Tektonには、ほかのプロダクトにはない次の特徴がありました。これらは私達のユースケースに合致するものでした。 Kubernetes Nativeなソフトウェアのため、宣言的記述・スケーリングなどのk8sが持つメリットを活用できる。 最小の処理単位(step)をコンテナとして作るため処理を再利用しやすく、また既存のコンテナ資産を流用できる。 一連のシーケンシャルな処理の集合(Task)の順序関係を定義して、複雑なパイプラインを構築できる。 TaskをPodとして実現するため、各処理間(コンテナ間)でリソースをシェアしやすい。 これらの特徴を踏まえて、私達が目指すDevOpsの仕組みを実現するにはTektonを利用するのが良いだろうと総合的に判断し、選択しました。 おわりに Tektonのプロジェクトは開発が活発であり、どのコンポーネントも進化スピードが早いです。今回はTekton Pipelinesの基本的な機能の紹介のみでしたが、今後、新機能や変更点に関するキャッチアップ、運用してみての知見などを紹介していきたいと考えています。 Qmonus Value Streamチームは、メンバ一人一人が、利用者であるアプリケーション開発者のために信念を持って活動を続けています。 プラットフォームを内製開発するポジション、プラットフォームを使ってNTTグループのクラウド基盤やCI/CDを構築支援するポジションそれぞれで、一緒に働く仲間を募集しています。興味を持たれた方は応募いただけると嬉しいです。 CI/CDプラットフォーム開発、ソフトウェアエンジニア NTTグループのクラウド基盤構築およびCI/CD導入、ソリューションアーキテクト 次回は、CI/CDパイプライン作成時のパラメータバインディングの課題と、Qmonus Value Streamにおける改題解決のアプローチについて紹介します。お楽しみに!
アバター
こんにちは、イノベーションセンター Agile CoE PJリーダーの岩瀬(以下、iwashi)です。 NTT Comで技術顧問をされており、アジャイルコーチでもある吉羽さん(以下、ryuzee)に「普段からどうやって学んで、どうやって活用しているのか?」という問いを1on1で投げかけてみました。本記事では、1on1模様の一部を公開します。 情報収集やナレッジマネジメントのテクニックが載っていますので、興味ある方はぜひご覧ください。というわけで、早速いってみましょう! RSSリーダーを使って購読 iwashi: ryuzeeさんは海外記事の翻訳など、かなり幅広く情報収集をされている印象があります。普段はどうやって情報をみつけているんですか? ryuzee: RSSリーダーにたくさん登録しています。最近はRSSリーダーが下火で悲しいんですが、feedlyを使って色々なRSSを購読しています。 海外の記事と国内の記事の両方が対象です。 はてなブックマークの記事には タグごとにRSSを出力 できるので、それで登録しているものもあります。 たとえば、Scrumってついていたり、Agileってついていたりするのはダイレクトに記事をみつけられます。 もちろん、玉石混交になるため、すべての記事をじっくり読むわけでもありませんが、世の中の意見はだいぶわかります。 iwashi: なるほど。個人的にはSlackで特定のチャンネルにRSSを流して確認しています。ryuzeeさんは何を使って中身を見ているのでしょうか? ryuzee: Macだと Reeder というRSS専用アプリがあって、それを使ってます。 無限連鎖で情報を探す iwashi: そもそもRSSで購読する種はどうやって探していますか? ryuzee: 良いサイトから無限連鎖するんですよ。良い記事は、良い情報を引用していることが多いので、引用先を見に行っていくつか読んでみて、良かったら購読してます。 あとはTwitterで海外の人をフォローしているので、そこで流れているサイトで良さそうなものがあれば追加しています。ちなみに、Twitterだと by name で探しています。たとえば、 Management 3.0 の @jurgenappelo や、スクラムの @jeffsutherland とか。他には認定スクラムトレーナーの有名人をフォローしていると、たまに何かしら流れてくるので、その辺の情報を確認しています。 iwashi: そうしていくと、RSSの量がかなり多そうなんですが、いつ見ているんですか? ryuzee: いつでも見ていますよ(笑)。四六時中、時間が空いたら。特に、朝一に見ることが多いです。コーヒーを飲みながら読んでます。 もちろん1回で全部読むというわけではなくて、スクリーニングしてしっかり読む・読まないを識別しています。 たとえば、「これは知っているから読まなくていい」「これはよく知らないからちゃんと読もう」など。 iwashi: トリアージみたいな感じです。 英語記事の対応 iwashi: 英語の記事を読むときは、どうされていますか? ryuzee: ざっと読むときはまるごとDeepLに突っ込んでしまうこともあります。 また、構造やフレームワークを説明する記事は、そのパターンを再利用できることも多いので、仕事の道具箱に加えるイメージで Obsidian に入れています。Obsidianに入れるときは、タグをつけて他のメモとリンクを貼っています。 こうやって整理していくと毎日メモが増えていく感じになり、日本語・英語関係なく増えています。 iwashi: Twitterのタイムラインの途中に英語があると、目が滑っちゃう問題ってありませんか? ryuzee: 海外の情報源は、英語専用のリストを作って TweetDeck のリストで見ています。ある程度真面目に読んでます。 それから、TwitterだとOGP画像が含まれているので、それも識別材料になります。 日々の「メンテナンス」がすべて iwashi: 少し話題を変えて、ここまで読んでいた記事の内容を実際の業務に活かすコツってありますか? コンテキストが同じというものは滅多にないので、そのまま活かせるものは少ない、と考えているのですがどうでしょうか? ryuzee: 毎日メンテナンスしておくこと、これに尽きます。メンテナンスしておいた内容はすぐに取り出して活用できるようになります。 クリップツールやメモツールは無数にあるんですが、メンテナンスしないでWebページをクリップし続けると、ゴミ箱になって2度と見なくなります(笑)。ナレッジベースは常にメンテナンスするのが大事です。 たとえば、スクラムのトレーニングを業務で提供していますが、よく聞かれる質問ってあるんですよ。ナレッジベースにそのあたりは溜めてあります。 iwashi: 以前に、あるWebクリップツールを使っていたんですが、メンテナンスしておらず、途中で読まなくなってしまったのを思い出しました。たしかにメンテナンスが大事ですね…。 ryuzee: そうなんです。ツールに関係なくゴミ箱になります。どんなツールを使っていても、日々メンテナンスしないと捨てることになる。もちろん日々のメモとして書き捨てのものは構わないですよ。 iwashi: これってシンプルなんですけど、なぜできないんでしょうね? 大変だからですか? ryuzee: やっただけのリターンが得られないと続かないんです。コンサルティング業やコーチ業は圧倒的瞬発力が求められます。いろんなネタを瞬時に投下しないといけない。だから、認定スクラムマスターの研修だと、たとえば数百個個ぐらいはコピペで応えられる情報を用意してあります。 そういったものは全部 Obsidian にいれてます。過去のスクラムガイドも全部入ってます。 最近だと さらば、翻訳調の文章! 技術者向け校正ルール という10年以上も前の記事が素晴らしかったので、早速Obsidianに入れました。ただし、Obsidianに入れるときはhtmlを除去して、plain markdown にする必要があるので、ツールも使います。ある程度、手をかけてます。 iwashi: ナレッジベースがどんどん大きくなると、もう必要ないメモも増えていきますよ。これはどうされているんですか? ryuzee: 不要になったテストコードを捨てるのと一緒で、いらなくなったメモは捨てています。上位互換の記事が出て、それだけ見ればよいときもありますし。過去のものとの差分が必要になるときもありますが、多くの場合はいりません。差分が必要なのは、スクラムガイドなどです。 iwashi: なるほど。それから記事・メモが増えたときによくある困ることとして、探し出す難しさがあるかと思います。これはどのように対応されていますか? ryuzee: よくある方法にフォルダによる整理がありますが、あれは無理筋です。1つの断面に切れないんですよ。そのため、タグをつけてあとは検索、という感じになります。 アプリもよりますが、検索が強いものは、検索で十分にがんばれます。Obsidianが最高なのは、中身がマークダウンであり、テキストファイルという点です。テキストファイル最高!という話は、 達人プログラマー の「プレインテキストの威力」という話にもつながります。 iwashi: たしかにテキストファイルの互換性は、時代を経たとしても強いです。30年前のファイルでも読めますし、30年後でも読めると思います。似た話で、 画像をスクリーンショットで作っていくとよい、なぜならば画像も時間が経ってから読める という記事も最近読んでいて通じるものがあると思いました。 長くなってきたので、そろそろ終わりにします。色々と教えていただきありがとうございました! まとめ ということで今回は、技術顧問のryuzeeさんがどうやって学んでいるのか?どうやって学びを活用しているのか?というテーマで1on1した話を共有してみました。 ナレッジマネジメントは、個々人によってスタイルが違うものかと思いますが、少しでも参考にできる部分があれば幸いです!
アバター
はじめに こんにちは、イノベーションセンターの鈴ヶ嶺です。普段はクラウドサービスをオンプレ環境でも同様のUI/UXで使用を可能とするハイブリッドクラウド製品の技術検証をしています。 NTT Comでは以下の過去の記事のように、AWSのハイブリッドクラウドソリューションAWS Outposts ラックの導入や技術検証を進めています。 engineers.ntt.com engineers.ntt.com engineers.ntt.com 本記事では、AWS Outpostsで実現するオンプレ環境におけるデータレイクのユースケースについて紹介します。 データレイクとは構造化データ、非構造化データに関わらず全てのデータを一元的に保存可能なストレージを意味しています。 このユースケースにより、低遅延性が求められる、もしくは秘匿性の高い大規模なデータをオンプレ環境で一元的に取り扱うことが可能となります。 オンプレ環境におけるデータレイクの背景と課題 膨大なユーザデータやIoTセンサーデータなどのビックデータを企業が適切に管理と分析を行う事が期待されています。そこから、社会の中で企業は更にサービスの質を向上させていくことが求められていることにより、データレイクの重要性は年々増しています。 データレイクを利用する中で、特に高速にデータ処理するために低遅延性が求められるケース、秘密情報を取り扱うためにセキュリティ保護上データの所在をコントロールしたいケースなどではオンプレ環境に構築する必要があります。 次にオンプレ環境にデータレイクを構築する場合の課題を3つ挙げます。 ストレージ・データ分析基盤の構築 オンプレ環境に大規模なデータを安価に格納可能なストレージや、それらのデータを処理するための並列分散処理基盤の構築が必要となります。これらはデータや分析需要の増加に対応するため、スケーラブルな設計を考慮している事が求められます。 保守運用コスト それぞれのストレージ・並列分散処理基盤のハードウェア管理やソフトウェアのversion管理などの保守運用も多くの稼働が必要となります。故障対応なども製品ごとにエスカレーション先が異なる場合に製品同士の依存関係を把握して対応する稼働コストも製品数に比例して増大すると考えられます。 オンプレ環境のセキュリティ 構築したオンプレサービス群に対して適切なアカウント管理やリソースのアクセス制限などのセキュリティ対策が必要です。オンプレ環境では、特に不十分な例として認証方式の不統一や共通されたパスワード運用による脆弱性が問題視されています。 AWS Outpostsで実現するオンプレデータレイク 先ほど述べた3つのストレージ・データ分析基盤の構築、保守運用コスト、オンプレ環境のセキュリティの課題を解決するためにAWSのハイブリッドクラウドであるAWS Outpostsを用いたオンプレデータレイクシステムを提案します。 S3 on Outposts、Auto Scaling Groupを活用したスケールするストレージ・データ分析基盤の構築 AWS Outpostsには事前にデータサイズを決定せずに大規模データを貯蔵可能な S3 on Outposts というオブジェクトストレージサービスがあります。また、オンプレ環境で Auto Scaling Group を利用することで柔軟にスケールする並列分散基盤を作成可能です。これにより先の述べたストレージ・データ分析基盤の構築の課題を解決できます。 AWSによる保守運用の一括管理 AWS Outpostsはフルマネージド型インフラストラクチャであり、ハードウェアの故障対応なども含めて全て一括でAWSが管理することで高い稼働コストを削減できます。 AWS IAMによるセキュリティ対策 AWS Outpostsの上で動作するリソースは全て AWS IAM というきめ細かなアクセス制御を担うサービスで管理できるので最小限のアクセス権限やアカウントの二段階認証導入による対策により高いセキュリティを担保可能です。 アーキテクチャ ここからAWS Outpostsで実現するオンプレデータレイクのアーキテクチャとそれぞれのシステム詳細について紹介します。 1. オンプレ環境の多数のIoTセンサーデータ オンプレ環境の多数のIoTセンサーデータをAWS Outpostsに送信します。AWS Outpostsは Local Gateway を用いることでオンプレ環境と通信できます。これによりセキュリティ要件上オンプレ環境からのみ取得可能なデータにアクセスすることを可能とし、定められた時間内に処理することが求められるデータを低遅延に蓄積・処理が可能となります。 2. Spark・Delta Lakeによるデータ更新 データレイクには Delta Lake というデータレイクの信頼性、セキュリティ、性能を高め、ストリーミング/バッチ処理の両方を柔軟に対応するオープンソースのストレージレイヤーを利用します。このストレージレイヤーをAuto Scaling Groupを用いて並列に構築された Spark で実行します。これによりスケーラビリティを担保して大規模なデータを蓄積・処理が可能となります。 3. S3 on Outpostsによるセキュアなビックデータ管理 S3 on Outpostsは事前にデータサイズを決定する必要がない、柔軟に大規模なデータをオンプレ環境に貯蔵可能なオブジェクトストレージサービスです。Delta Lakeで処理されたデータはこのS3 on Outpostsに貯蔵されます。2022年8月時点でS3 on Outpostsに貯蔵可能なデータ量の最大は380TBです。 また、S3 on Outpostsはインターネットなどの外部からデータにアクセス不可能で、AWS Outpostsやオンプレ環境からのみアクセス可能です。さらにAWS IAMを用いることで最小限のデータアクセス制御を実装可能です。 これらのことからS3 on Outpostsによって、オンプレ環境でセキュアなビックデータの管理を実現可能です。 データレイクの性能検証 ここでは、データレイクの性能を検証します。 データセットとしてUCI Machine Learning Repositoryの KASANDR Data Set を利用します。これはヨーロッパのeコマース広告のKelkoo社の顧客の行動を記録したデータです。今回は学習データのtrain_de.csv(3.14GB)を使用します。 以下に実際のデータ例を抜粋しました。時刻データやカテゴリデータが含まれるデータとなっています。 userid offerid countrycode category merchant utcdate rating fa937b779184527f12e2d71c711e6411236d1ab59f8597d7494af26d194f0979 c5f63750c2b5b0166e55511ee878b7a3 de 100020213 f3c93baa0cf4430849611cedb3a40ec4094d1d370be8417181da5d13ac99ef3d 2016-06-14 17:28:47.0 0 f6c8958b9bc2d6033ff4c1cc0a03e9ab96df4bcc528913be886254d1ad8c7c44 19754ec121b3a99fff3967646942de67 de 100020213 21a509189fb0875c3732590121ff3fc86da770b0628c1812e82f023ee2e0f683 2016-06-14 17:28:48.0 0 02fe7ccf1de19a387afc8a11d08852ffd2b4dabaed4e2d2af9130e9c5dfb9ee8 5ac4398e4d8ad4167a57b43e9c724b18 de 125801 b042951fdb45ddef8ba6075ced0e5885bc2fa4c4470bf790432e606d85032017 2016-06-14 17:28:50.0 0 9de5c06d0a16256b13b8e7cdc50bf203ecef533eb5cbe18d514947b1c5296f33 be83df9772ec47fd210b28091138ff11 de 125801 4740b6c83b6e12e423297493f234323ffd1c991f3d4496a71daebbef01d6dcf0 2016-06-14 17:29:19.0 0 データサイズの比較 次のように、データセットをDelta Lake形式でS3 on Outpostsに保存します。 実際の処理はspark-shell 3.3.0を用いました。 2022年8月時点では、以下のようにAmazon EMR on OutpostsではS3 on Outpostsをサポートしていないため、S3 on Outpostsへのデータ保存に対応するため、 Hadoop-AWS に対して改善パッチを当てたものを使用しました。 また、Hadoopに改善パッチ取り込みを 要求中 です。 S3 is the only supported option for Amazon EMR on Outposts. S3 on Outposts is not supported for Amazon EMR on AWS Outposts. https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-plan-outposts.html 以下がS3 on OutpostsにDelta Lake形式でデータを保存するスクリプトです。 sc.hadoopConfiguration.set( "fs.s3a.bucket.delta-bucket.accesspoint.arn" , "arn:aws:s3-outposts:ap-northeast-1:123456789:outpost/op-123456789/accesspoint/delta-lake" ) val base_path = "s3a://delta-bucket/delta" val df = spark.read.format( "csv" ).option( "header" , "true" ).option( "inferSchema" , "true" ).option( "delimiter" , "\t" ).load( "train_de.csv" ) df.write.format( "delta" ).mode( "overwrite" ).save(base_path) データの実態は次のように Apache Parquet として保存されます。 > aws s3 ls s3://arn:aws:s3-outposts:ap-northeast-1:123456789:outpost/op-123456789/accesspoint/delta-lake --summarize --recursive 2022-07-14 09:36:17 21697 delta/_delta_log/00000000000000000000.json 2022-07-14 09:34:43 55352961 delta/part-00000-2de3a57b-1c68-4679-8c9d-c950c7e45de6-c000.snappy.parquet 2022-07-14 09:34:43 55539573 delta/part-00001-884e0e49-84d9-49c3-933e-9eddb364f674-c000.snappy.parquet 2022-07-14 09:34:52 55236752 delta/part-00002-e64a5825-7425-4435-aace-7fd77d4ba19a-c000.snappy.parquet 2022-07-14 09:34:52 55669516 delta/part-00003-d15c00cc-a2b9-4be0-9029-9132fa14a98f-c000.snappy.parquet 2022-07-14 09:35:01 55512327 delta/part-00004-73c45fe3-7572-4989-8b68-63b786f2c328-c000.snappy.parquet 2022-07-14 09:35:01 55560218 delta/part-00005-94f8f7aa-b2e5-4d61-93bd-94b11518c662-c000.snappy.parquet 2022-07-14 09:35:09 55337097 delta/part-00006-4bc8a8ae-67f9-4941-967e-4392d78161e6-c000.snappy.parquet 2022-07-14 09:35:09 55284570 delta/part-00007-e8d4d3f6-f550-4321-afbe-8ca4dd50277f-c000.snappy.parquet 2022-07-14 09:35:18 55354729 delta/part-00008-f442a193-1286-46a2-80f2-eeb25e4c75ec-c000.snappy.parquet 2022-07-14 09:35:18 55283349 delta/part-00009-973a1e4f-cccd-484e-bf59-72db29b42e3e-c000.snappy.parquet 2022-07-14 09:35:26 55466100 delta/part-00010-dd722212-766e-434c-b6d9-772bb4ca5293-c000.snappy.parquet 2022-07-14 09:35:26 55163670 delta/part-00011-e324ec88-218d-426f-bc2a-a982538f9bde-c000.snappy.parquet 2022-07-14 09:35:34 55244413 delta/part-00012-9789b931-a5b8-4ea6-957c-6d6d9c751821-c000.snappy.parquet 2022-07-14 09:35:35 55321192 delta/part-00013-b7a43423-2a53-4a41-a6f4-a8883083315c-c000.snappy.parquet 2022-07-14 09:35:42 55569791 delta/part-00014-c1b90126-3e54-4730-8ffa-7ab79a9ab112-c000.snappy.parquet 2022-07-14 09:35:44 55471295 delta/part-00015-69d1f3b7-b7ac-4357-af09-1c45ba360a3a-c000.snappy.parquet 2022-07-14 09:35:50 55426860 delta/part-00016-a276226c-169c-40b2-9cb7-c8b28d335160-c000.snappy.parquet 2022-07-14 09:35:53 55399812 delta/part-00017-2275eb45-d961-41f3-8e6f-46cd5b59bf11-c000.snappy.parquet 2022-07-14 09:35:58 55366224 delta/part-00018-38c48ee9-98f8-44f8-a30d-5898cb962e3f-c000.snappy.parquet 2022-07-14 09:36:01 55470407 delta/part-00019-05744950-7c1f-439a-a2d9-8515f21b87c6-c000.snappy.parquet 2022-07-14 09:36:06 55318799 delta/part-00020-99108c63-21e3-4560-9667-c9dfd2cce99e-c000.snappy.parquet 2022-07-14 09:36:09 55274819 delta/part-00021-cada37d7-8ecc-4d4c-8bac-97afde209cb3-c000.snappy.parquet 2022-07-14 09:36:14 55666013 delta/part-00022-16bff2b2-2346-4507-9fde-08f3d60d0f5b-c000.snappy.parquet 2022-07-14 09:36:13 22632601 delta/part-00023-b13c0d3b-e3a7-46f7-a345-fde187d4d08e-c000.snappy.parquet Total Objects: 25 Total Size: 1296944785 データ形式 データサイズ CSV 3.14GB Delta Lake 1.30GB このDelta Lake形式で保存されたデータと元のCSVデータのデータサイズを上記の表を用いて比較します。Delta Lake形式にすることでCSV形式よりもおよそ59%のデータサイズが削減可能です。これはDelta Lakeの内部形式であるApache Parquetの高い圧縮率が主要な要因であると考えられます。 データ処理時間の比較 データ処理時間の比較のため、S3 on OutpostsにCSV, Delta Lake形式で同様のデータを保存し処理の時間をそれぞれ30回計測します。処理内容は次のように特定のカラム(category)のグループ集計をとるものとします。 sc.hadoopConfiguration.set( "fs.s3a.bucket.delta-bucket.accesspoint.arn" , "arn:aws:s3-outposts:ap-northeast-1:123456789:outpost/op-123456789/accesspoint/delta-lake" ) // CSV形式で処理する時間を計測する val csv_path = "s3a://delta-bucket/csv/train_de.csv" val csv_df = spark.read.format( "csv" ).option( "header" , "true" ).option( "inferSchema" , "true" ).option( "delimiter" , "\t" ).load(csv_path) spark.time(csv_df.groupBy( "category" ).count().show()) // Delta Lake形式で処理する時間を計測する val base_path = "s3a://delta-bucket/delta" val delta_df = spark.read.format( "delta" ).load(basePath) spark.time(delta_df.groupBy( "category" ).count().show()) データ形式 処理時間(sec) CSV 26.3 Delta Lake 4.3 上記が平均の処理時間の表になります。Delta Lake形式の方がCSV形式よりも高速に処理可能であることが分かります。CSVのデメリットとしてデータ形式上特定の列データ(category)のみを読み込むことができないため、今回の処理では全データを読み込むオーバーヘッドが生じます。一方、Delta Lake形式は内部形式Apache Parquetが次の画像のように列指向フォーマットで保存されているため特定の列データ(category)のみを読み込むことが可能となり、不必要なデータを読み飛ばしたことでCSVよりも高速に処理できました。 https://parquet.apache.org/docs/file-format/ まとめ 以上のようにAWS Outpostsを用いることでオンプレ環境にスケーラブルなストレージ・分析基盤の構築や、稼働コストを抑えた保守運用、AWSサービスを包括的に使用することで高いセキュリティを達成可能であることを紹介しました。 また、性能検証の結果から従来のようにCSV形式のままで保存するよりもDelta Lakeを利用することでデータサイズの大幅な圧縮とデータ処理時間の高速化というメリットが得られることを示しました。
アバター
サマリ Multi-AS で構成されるネットワークにおける Traffic Engineering (TE) の実現 IOS XR の機能を用いて、 TE metric に基づき AS Border Router(ASBR) を選択する TE の動作検証に成功 この記事は Multi-AS Segment Routing 検証連載の第 5 回です。目次は こちら 概要 イノベーションセンターの岩田です。本記事では、Multi-AS での Segment Routing(SR) での Traffic Engineering(TE)の実現手法についてご紹介します。 Multi-AS において SR-TE で実現したい要求としては大きく以下の 2 つに分けられます。 (a) AS 内で意図した経路を選択したい(AS 内での TE) (b) 他 AS との接続トポロジーを考慮した経路を選択したい(AS 間での TE) 本記事ではこの 2 つの TE を SR でどう実現するかについてご紹介します。 特に (a) では出口となる ASBR の選択を AS 内で行う必要があります。そのために SR-TE の metric に基づいた egress node(ASBR)選択の実現例について検証パートでご紹介します。 (a) AS 内での TE Multi-AS で AS 内での TE を実現する上での考慮点として、ある 1 つの宛先に対し egress node(ASBR)となる node 候補が複数存在する際にどう TE を実現するか、という点があります。 1 つの宛先に対し、1 つの ASBR のみが egress node となる場合の TE は endpoint の選択余地がないので Single-AS と同様に考えることができます。これに関しては 第 4 回の記事 で紹介したのでそちらをご参照ください。 ここでは、以下の図のような 1 つの ingress node に対し 複数の ASBR が egress node になれる構成時の TE について考えます。 SR-TE を用いた経路制御をする場合には、SR Policy で定義した metric に基づいて、より優位な ASBR を選択するような経路を選択したいという要求が考えられます。通常は、各 BGP 経路の egress node となる ASBR は BGP ベストパス決定アルゴリズムにより決定されてしまいます 1 。 SR-TE で ASBR の選択をする場合には、 ingress node において何らかの方法で SR Policy の評価結果を BGP のベストパスに反映させる必要があります。 この要求を実現する機能として、 BGP ベストパス決定アルゴリズム時に SR Policy を評価するための機能が、IOS XR 7.3.2 以降で提供されています 2 。本記事の検証パートでは、この機能を用いて ASBR 選択を含めた Color-Based Steering を行うための設定例をご紹介します。 (b) AS 間での TE AS 間での TE の実現手法としては BGP Egress Peer Engineering(EPE) があります。EPE は ASBR において Inter-AS 情報から BGP Peering Segments、もしくは BGP Peering SIDs と呼ばれる segment を作成し IGP へ広告することで、AS 内の ingress node から EBGP Peer の選択を可能とします。これにより、接続した AS も含めた TE が実現されます。 本連載で我々が取り組んでいる Inter-AS Option-B において EPE を用いた TE を実現するためには、 ASBR において VPN SID と BGP Peering SID の 2 つを同時に処理する必要がありますが、2022 年 8 月時点ではどの機器にも実装されていないため本記事では扱いません。 検証 本章では、ある宛先に対し複数の egress node(ASBR)が選択可能な Multi-AS/Multi-domain SR-MPLS 上での L3VPN において、ASBR 選択を含めた Color-Based Steering を行うための設定例をご紹介します。 トポロジー 下記のようなトポロジーを作成します。 3 つのルーターによる AS を 2 つ接続したコア網で、顧客 A を収容します。 検証環境は Multi-vendor に構成します。使用する機器は下記の通りです。 SR Domain 1 PE1、ASBR1: Cisco IOS XR 7.4.1 ASBR2: Junos OS 21.3R1.9 SR Domain 2 ASBR3: Cisco IOS XR 7.4.1 PE2、ASBR4: Junos OS 21.3R1.9 検証の流れ 本検証では、下図に示す AS65000 の各リンクの TE metric を設定し変更することにより ASBR の選択が変わることを示します。 具体的には ingress node(PE) において定義された、 TE metric を指標とした dynamic path を計算する SR Policy が、より小さい metric の経路を選択することを確認します。 また設定は、以下の手順で実施していきます。 TE metric に基づく経路制御をする SR Policy を作成する PE1 から ASBR1 への経路の metric が最小となるように TE metric を設定し、意図通り ASBR1 が egress node として選択される事を確認する PE1 から ASBR2 への経路の metric が最小となるように TE metric を設定変更し、意図通り egress node が ASBR2 に変更される事を確認する アンダーレイの構築に関しては、 第 2 回 の検証例のアンダーレイと同様のため省略いたします。 1. TE metric に基づく経路制御のための SR Policy を作成する 各リンクで設定した TE metric を広告するための設定 本検証では、dynamic path を用いた TE により経路制御します。 各リンクへ設定した TE metric 情報を Traffic Engineering Database (TED) に反映し、経路制御を行えるように Cisco ルーターへ以下の設定を追加します。 router isis 1 distribute link-state address-family ipv4 unicast mpls traffic-eng level-2-only ! ! TE metric に基づく経路を選択するための SR Policy の設定 各 egress node(ASBR)を endpoint とする、 color が 100 に対応する SR Policy を作成します。以下の設定を PE1(XR)へすることにより PE と endpoint 間の各リンクの TE metric の合計値が最小となるような経路を表す segment list を生成します。 segment-routing traffic-eng policy path-based-te-metric-to-asbr1 color 100 end-point ipv4 10.255.0.2 candidate-paths preference 10 dynamic metric type te ! ! ! ! ! ! ! segment-routing traffic-eng policy path-based-te-metric-to-asbr2 color 100 end-point ipv4 10.255.0.3 candidate-paths preference 10 dynamic metric type te ! ! ! ! ! ! ! BGP color と SR Policy を関連付けを行う設定 本検証では Color-Based Steering を行うため、以下のように BGP 経路に紐付ける color を設定します。 PE1(XR) vrf 100 address-family ipv4 unicast export route-policy add-color-100 ! ! extcommunity-set opaque color-100 100 end-set ! route-policy add-color-100 set extcommunity color color-100 end-policy ! PE2(Junos) set policy-options community COLOR-100 members color:0:100 delete policy-options policy-statement EXPORT-POLICY-100 set policy-options policy-statement EXPORT-POLICY-100 term ROUTE-TARGET then community add VRF100-65001-RT set policy-options policy-statement EXPORT-POLICY-100 term ADD-COLOR-100 then community add COLOR-100 set policy-options policy-statement EXPORT-POLICY-100 term ROUTE-TARGET then community add VRF100-SHARED-RT set policy-options policy-statement EXPORT-POLICY-100 term REDIST-DIRECT from protocol direct set policy-options policy-statement EXPORT-POLICY-100 term REDIST-DIRECT then accept PE1 が PE2 から受け取った経路に color がついていることを確認します。 RP/0/RP0/CPU0:PE1#show bgp vpnv4 unicast vrf 100 192.168.1.0/24 Wed Jul 20 13:54:17.734 JST BGP routing table entry for 192.168.1.0/24, Route Distinguisher: 65000:100 Versions: Process bRIB/RIB SendTblVer Speaker 33 33 Last Modified: Jul 20 13:37:04.989 for 00:17:12 Paths: (1 available, best #1) Not advertised to any peer Path #1: Received by speaker 0 Not advertised to any peer 65001 10.255.0.3 C:100 (bsid:24009) (metric 10) from 10.255.0.3 (10.255.0.3) Received Label 16 Origin IGP, localpref 100, valid, internal, best, group-best, import-candidate, imported Received Path ID 0, Local Path ID 1, version 33 Extended community: Color:100 RT:64999:100 RT:65001:100 SR policy color 100, up, not-registered, bsid 24009 Source AFI: VPNv4 Unicast, Source VRF: default, Source Route Distinguisher: 65001:100 PE2 が PE1 から受け取った経路に color がついていることを確認します。 user@ASBR2> show route table bgp.l3vpn.0 detail bgp.l3vpn.0: 2 destinations, 4 routes (2 active, 0 holddown, 0 hidden) 65000:100:192.168.0.0/24 (2 entries, 0 announced) *BGP Preference: 170/-101 Route Distinguisher: 65000:100 Next hop type: Indirect, Next hop index: 0 Address: 0x704e3d4 Next-hop reference count: 3 Source: 10.255.0.3 Next hop type: Router, Next hop index: 644 Next hop: 10.0.1.2 via ge-0/0/1.0, selected Label operation: Push 16 Label TTL action: prop-ttl Load balance label: Label 16: None; Label element ptr: 0x782ed48 Label parent element ptr: 0x782ea50 Label element references: 1 Label element child references: 0 Label element lsp id: 0 Session Id: 0x142 Protocol next hop: 10.255.0.3 Label operation: Push 16 Label TTL action: prop-ttl Load balance label: Label 16: None; Indirect next hop: 0x7199084 1048575 INH Session ID: 0x143 State: <Active Int Ext ProtectionPath ProtectionCand> Local AS: 65001 Peer AS: 65001 Age: 23:07 Metric2: 10 Validation State: unverified ORR Generation-ID: 0 Task: BGP_65001.10.255.0.3 AS path: 65000 ? Communities: target:64999:100 target:65000:100 color:0:100 Import Accepted VPN Label: 16 Localpref: 100 Router ID: 10.255.0.3 Secondary Tables: 100.inet.0 Thread: junos-main BGP Preference: 170/-101 Route Distinguisher: 65000:100 Next hop type: Indirect, Next hop index: 0 Address: 0x704e8b4 Next-hop reference count: 2 Source: 10.255.0.2 Next hop type: Router, Next hop index: 0 Next hop: 10.0.0.2 via ge-0/0/0.0, selected Label operation: Push 24005 Label TTL action: prop-ttl Load balance label: Label 24005: None; Label element ptr: 0x782ee60 Label parent element ptr: 0x782ea50 Label element references: 1 Label element child references: 0 Label element lsp id: 0 Session Id: 0x0 Protocol next hop: 10.255.0.2 Label operation: Push 24005 Label TTL action: prop-ttl Load balance label: Label 24005: None; Indirect next hop: 0x71ac104 - INH Session ID: 0x0 State: <NotBest Int Ext Changed ProtectionPath ProtectionCand> Inactive reason: Not Best in its group - IGP metric Local AS: 65001 Peer AS: 65001 Age: 22:12 Metric2: 20 Validation State: unverified ORR Generation-ID: 0 Task: BGP_65001.10.255.0.2 AS path: 65000 ? Communities: target:64999:100 target:65000:100 color:0:100 Import Accepted VPN Label: 24005 Localpref: 100 Router ID: 10.255.0.2 Secondary Tables: 100.inet.0 Thread: junos-main SR Policy の評価結果を BGP ベストパスに反映するための設定 BGP ベストパス選択アルゴリズムの評価時に SR Policy が評価されるように、 PE1 に以下のような設定を追加します。 router bgp 65000 nexthop validation color-extcomm sr-policy bgp bestpath igp-metric sr-policy ! この設定を追加すると Cisco ルーターの BGP ベストパス選択アルゴリズム 3 の Step 8 において、 IGP metric の代わりに SR Policy path metric が評価されるようになります。Step 8 よりも優先度の高い評価項目である local preference や MED が設定されている場合は SR Policy の評価前にベストパスが決定し、経路へ反映されない事がある点にご注意ください。 また、SR Policy で定義できる metric type は以下の 5 つがあり、この機能において SR Policy の 優先度は administrative distance values(admin values)として表されます。SR Policy の metric type と 対応する admin values は以下の通りで、値が小さいほど優先されます。 latency(admin values: 10) TE(admin values: 20) IGP(admin values: 30) hopcount(admin values: 40) NONE/UNKNOWN metric type (for explicit segment list policies) (admin values: 100) 作成した SR Policy の確認 定義した SR Policy の評価結果を確認します。 まだ各リンクへ TE metric を設定していないため、両 SR Policy 共に path accumulated metric が 10(1リンク当たりのデフォルト値)と計算されている事が分かります。 RP/0/RP0/CPU0:PE1#show segment-routing traffic-eng policy Sat Jul 16 16:20:32.590 JST SR-TE policy database --------------------- Color: 100, End-point: 10.255.0.2 Name: srte_c_100_ep_10.255.0.2 Status: Admin: up Operational: up for 1d02h (since Jul 15 13:39:47.290) Candidate-paths: Preference: 10 (configuration) (active) Name: path-based-te-metric-to-asbr1 Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) metric Type: TE, Path Accumulated metric: 10 16001 [Prefix-SID, 10.255.0.2] Attributes: Binding SID: 24008 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Color: 100, End-point: 10.255.0.3 Name: srte_c_100_ep_10.255.0.3 Status: Admin: up Operational: up for 00:00:05 (since Jul 16 16:20:26.833) Candidate-paths: Preference: 10 (configuration) (active) Name: path-based-te-metric-to-asbr2 Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) metric Type: TE, Path Accumulated metric: 10 16002 [Prefix-SID, 10.255.0.3] Attributes: Binding SID: 24009 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no path accumulated metric が等しい場合、この時点では ASBR1(10.255.0.2)が egress node として選択されていることが確認できました。 RP/0/RP0/CPU0:PE1#show route vrf 100 Sat Jul 16 16:51:50.976 JST Codes: C - connected, S - static, R - RIP, B - BGP, (>) - Diversion path D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 E1 - OSPF external type 1, E2 - OSPF external type 2, E - EGP i - ISIS, L1 - IS-IS level-1, L2 - IS-IS level-2 ia - IS-IS inter area, su - IS-IS summary null, * - candidate default U - per-user static route, o - ODR, L - local, G - DAGR, l - LISP A - access/subscriber, a - Application route M - mobile route, r - RPL, t - Traffic Engineering, (!) - FRR Backup path Gateway of last resort is not set C 192.168.0.0/24 is directly connected, 00:05:33, GigabitEthernet0/0/0/2 L 192.168.0.254/32 is directly connected, 00:05:33, GigabitEthernet0/0/0/2 B 192.168.1.0/24 [200/0] via 10.255.0.2 (nexthop in vrf default), 00:04:17 以下のように VPN 経路を確認すると (admin 20) と出力され、admin values が 20 である TE metric に基づいた SR Policy が適用されていることも確認できました。 RP/0/RP0/CPU0:PE1#show bgp vpnv4 unicast vrf 100 192.168.1.0/24 Wed Jul 20 14:00:12.294 JST BGP routing table entry for 192.168.1.0/24, Route Distinguisher: 65000:100 Versions: Process bRIB/RIB SendTblVer Speaker 42 42 Last Modified: Jul 20 13:59:56.989 for 00:00:15 Paths: (1 available, best #1) Not advertised to any peer Path #1: Received by speaker 0 Not advertised to any peer 65001 10.255.0.2 C:100 (bsid:24008) (admin 20) (metric 10) from 10.255.0.2 (10.255.0.2) Received Label 24005 Origin IGP, localpref 100, valid, internal, best, group-best, import-candidate, imported Received Path ID 0, Local Path ID 1, version 42 Extended community: Color:100 RT:64999:100 RT:65001:100 SR policy color 100, up, not-registered, bsid 24008 Source AFI: VPNv4 Unicast, Source VRF: default, Source Route Distinguisher: 65001:100 これで準備は完了です。次の節から各リンクへ TE metric を設定することでより metric 値が小さい経路となる ASBR が選択できることを確認していきます。 2. PE1、ASBR1 間経路の metric が最小となるように設定し、ASBR1 が egress node として選択される事を確認する 上図のように、TE metric を設定し、ASBR1 が egress node として選択される事を確認していきます。 TE metric の設定 PE1 と ASBR1 間に metric を設定(PE1) segment-routing traffic-eng interface GigabitEthernet0/0/0/0 metric 50 ! ! ! PE1 と ASBR2 間に metric を設定(PE1) segment-routing traffic-eng interface GigabitEthernet0/0/0/1 metric 100 ! ! ! ASBR1 と ASBR2 間に metric を設定(ASBR1) segment-routing traffic-eng interface GigabitEthernet0/0/0/1 metric 100 ! ! ! SR Policy の評価結果 以下のように、PE1 と ASBR1 間の metric が 50、PE1 と ASBR2 間の metric が 100 とそれぞれ計算されていることが確認できました。 RP/0/RP0/CPU0:PE1#show segment-routing traffic-eng policy Wed Jul 20 14:09:55.662 JST SR-TE policy database --------------------- Color: 100, End-point: 10.255.0.2 Name: srte_c_100_ep_10.255.0.2 Status: Admin: up Operational: up for 00:37:58 (since Jul 20 13:31:57.085) Candidate-paths: Preference: 10 (configuration) (active) Name: path-based-te-metric-to-asbr1 Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) Metric Type: TE, Path Accumulated Metric: 50 16002 [Prefix-SID, 10.255.0.2] Attributes: Binding SID: 24008 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Color: 100, End-point: 10.255.0.3 Name: srte_c_100_ep_10.255.0.3 Status: Admin: up Operational: up for 00:37:58 (since Jul 20 13:31:57.087) Candidate-paths: Preference: 10 (configuration) (active) Name: path-based-te-metric-to-asbr2 Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) Metric Type: TE, Path Accumulated Metric: 100 16003 [Prefix-SID, 10.255.0.3] Attributes: Binding SID: 24009 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no egress node として、ASBR1 が採用されていることも確認できました。 RP/0/RP0/CPU0:PE1#show bgp vpnv4 unicast vrf 100 192.168.1.0/24 detail Wed Jul 20 14:10:37.611 JST BGP routing table entry for 192.168.1.0/24, Route Distinguisher: 65000:100 Versions: Process bRIB/RIB SendTblVer Speaker 43 43 Flags: 0x00003001+0x00000000; Last Modified: Jul 20 14:09:45.989 for 00:00:51 Paths: (1 available, best #1) Not advertised to any peer Path #1: Received by speaker 0 Flags: 0xa000000005060005, import: 0x80 Not advertised to any peer 65001 10.255.0.2 C:100 (bsid:24008) (admin 20) (metric 50) from 10.255.0.2 (10.255.0.2), if-handle 0x00000000 Received Label 24005 Origin IGP, localpref 100, valid, internal, best, group-best, import-candidate, imported Received Path ID 0, Local Path ID 1, version 42 Extended community: Color:100 RT:64999:100 RT:65001:100 SR policy color 100, up, not-registered, bsid 24008 Source AFI: VPNv4 Unicast, Source VRF: default, Source Route Distinguisher: 65001:100 traceroute による経路確認 traceroute により、ASBR1 経由で転送されていることが確認できました。 RP/0/RP0/CPU0:PE1#traceroute 192.168.1.254 vrf 100 Wed Jul 20 14:11:09.185 JST Type escape sequence to abort. Tracing the route to 192.168.1.254 1 10.0.0.2 [MPLS: Label 24005 Exp 0] 56 msec 12 msec 7 msec 2 10.100.0.2 [MPLS: Label 24007 Exp 0] 29 msec 5 msec 15 msec 3 192.168.1.254 15 msec 3 msec 7 msec 3. PE1、ASBR2 間経路の metric が最小となるように設定し、ASBR2 が egress node として選択される事を確認する 上図のように、TE metric を設定し、ASBR1 が egress node として選択される事を確認していきます。 TE metric の設定 PE1 と ASBR1 間に metric を設定(PE1) segment-routing traffic-eng interface GigabitEthernet0/0/0/0 metric 100 ! ! ! PE1 と ASBR2 間に metric を設定(PE1) segment-routing traffic-eng interface GigabitEthernet0/0/0/1 metric 50 ! ! ! ASBR2 と ASBR1 間に metric を設定(ASBR2) set protocols isis interface ge-0/0/1.0 level 2 te-metric 100 SR Policy の評価結果 以下のように、PE1 と ASBR1 間の metric が 100、PE1 と ASBR2 間の metric が 50 とそれぞれ計算されていることが確認できました。 RP/0/RP0/CPU0:PE1#show segment-routing traffic-eng policy Wed Jul 20 14:11:44.058 JST SR-TE policy database --------------------- Color: 100, End-point: 10.255.0.2 Name: srte_c_100_ep_10.255.0.2 Status: Admin: up Operational: up for 00:39:47 (since Jul 20 13:31:57.085) Candidate-paths: Preference: 10 (configuration) (active) Name: path-based-te-metric-to-asbr1 Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) Metric Type: TE, Path Accumulated Metric: 100 16002 [Prefix-SID, 10.255.0.2] Attributes: Binding SID: 24008 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Color: 100, End-point: 10.255.0.3 Name: srte_c_100_ep_10.255.0.3 Status: Admin: up Operational: up for 00:39:47 (since Jul 20 13:31:57.087) Candidate-paths: Preference: 10 (configuration) (active) Name: path-based-te-metric-to-asbr2 Requested BSID: dynamic Protection Type: protected-preferred Maximum SID Depth: 10 Dynamic (valid) Metric Type: TE, Path Accumulated Metric: 50 16003 [Prefix-SID, 10.255.0.3] Attributes: Binding SID: 24009 Forward Class: Not Configured Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no egress node として、ASBR2 が採用されていることも確認できました。 RP/0/RP0/CPU0:PE1#show bgp vpnv4 unicast vrf 100 192.168.1.0/24 detail Wed Jul 20 14:12:06.724 JST BGP routing table entry for 192.168.1.0/24, Route Distinguisher: 65000:100 Versions: Process bRIB/RIB SendTblVer Speaker 49 49 Flags: 0x00003001+0x00000000; Last Modified: Jul 20 14:11:34.989 for 00:00:31 Paths: (1 available, best #1) Not advertised to any peer Path #1: Received by speaker 0 Flags: 0xa000000005060005, import: 0x80 Not advertised to any peer 65001 10.255.0.3 C:100 (bsid:24009) (admin 20) (metric 50) from 10.255.0.3 (10.255.0.3), if-handle 0x00000000 Received Label 16 Origin IGP, localpref 100, valid, internal, best, group-best, import-candidate, imported Received Path ID 0, Local Path ID 1, version 49 Extended community: Color:100 RT:64999:100 RT:65001:100 SR policy color 100, up, not-registered, bsid 24009 Source AFI: VPNv4 Unicast, Source VRF: default, Source Route Distinguisher: 65001:100 パケットカウンタによる経路確認 我々の環境では IOS XR の PE から Junos の ASBR を経由する経路に対し traceroute を実行すると Junos ルーターが応答せず経路確認できないため、ASBR2 と ASBR4 間リンクの ASBR2 側のインタフェース(ge-0/0/2)の Output packets を計測することにより、パケットが ASBR2 を経由したと確認していきます。 以下のように該当インタフェースの統計情報をクリアします。 user@ASBR2> clear interfaces statistics ge-0/0/2 user@ASBR2> show interfaces ge-0/0/2 statistics | match "Output packets" Output packets: 0 次に対向へパケットを一定数送ります。 これで疎通できていることは確認できました。 RP/0/RP0/CPU0:PE1#ping 192.168.1.254 vrf 100 count 1000 size 100 interval 1 Wed Jul 20 14:23:45.959 JST Type escape sequence to abort. Sending 1000, 100-byte ICMP Echos to 192.168.1.254, timeout is 2 seconds: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!! Success rate is 100 percent (1000/1000), round-trip min/avg/max = 1/4/21 ms インタフェースの Output packets を確認すると以下のようにパケットカウンタが増え、ASBR2 を経由して通信できていることが確認できました。 user@ASBR2> show interfaces ge-0/0/2 statistics | match "Output packets" Output packets: 1004 まとめ 本記事では、Multi-AS での SR-TE を実現する上での考慮点を紹介し、Multi-AS/Multi-domain SR-MPLS 上での L3VPN において、TE metric に基づいた ASBR 選択を含む Color-Based Steering による経路制御の検証結果を紹介しました。次回の記事では、Flexible Algorithm を用いた TE について紹介予定です。 (2022/08/22 追記) 公開しました: [Multi-AS Segment Routing 検証連載 #6] IGP Flexible Algorithm Segment Routing Policy Architecture https://datatracker.ietf.org/doc/html/rfc9256#section-2.5 ↩ Deploy BGP Soft Next-Hop in Cisco IOS XR, BGP Best Path Selection Forcing SR Policy Paths https://www.cisco.com/c/en/us/support/docs/ip/border-gateway-protocol-bgp/217744-deploy-bgp-soft-next-hop-in-cisco-ios-xr.html?dtid=osscdc000283#anc12 ↩ BGP Best Path Selection Algorithm https://www.cisco.com/c/en/us/support/docs/ip/border-gateway-protocol-bgp/13753-25.html ↩
アバター
はじめに DevOpsプラットフォームの取り組みを紹介する4回目の記事です。 Qmonus Value Stream の開発チームの會澤です。 連載4回目では、Qmonus Value Streamの重要な構成要素である CUE言語 についてご紹介します。 前回の記事 では、Infrastructuer as Code (以下IaC)の課題と、Cloud Native AdapterというQmonus Value Streamチームの独自技術について解説しました。 Cloud Native Adapterは、「インフラストラクチャの構成」と「ワークフロー」をCUE言語を使って宣言します。 CUE言語は、複雑なシステム構成をスケーラブルに管理できることから、近年注目を集めているデータ記述言語です。 前回の記事でご紹介したとおり、 KubeVela や Dagger など最近リリースされたIaCソリューションで採用されていることが多く、メルカリがKubernetes基盤を管理するために活用していることでも有名です 1 。 今回は、CUE言語の言語としての特徴と、Cloud Native Adapterの開発においてCUE言語をどのように活用しているかについてご紹介します。 Kubernetesにおけるアプリケーションのデプロイ GCPやAWS、Azureといったパブリッククラウド環境を利用してサービスを公開する際、Kubernetes上に「マニフェスト」を適用してアプリケーションをデプロイする機会は多いと思います。 以下のYAMLで記述されたマニフェストは、Kubernetesの 公式サイト から例として引用したものになります。 このようなマニフェストをKubernetesクラスタに適用することで、予め指定したパラメータを利用して、アプリケーションをクラウド上にデプロイできます。 apiVersion : apps/v1 kind : Deployment metadata : name : nginx-deployment labels : app : nginx spec : replicas : 3 selector : matchLabels : app : nginx template : metadata : labels : app : nginx spec : containers : - name : nginx image : nginx:1.14.2 ports : - containerPort : 80 こういったマニフェストを利用することで、インフラストラクチャのリソース構成や設定をコードで記述・適用し、同じ設定を再利用できます。 その一方で、YAMLを使ってマニフェストを管理することで、以下のような難しさを感じることもあるのではないでしょうか。 データのKeyとValueやリストの構造をインデントによって構成しているため、ネストのズレにより意図しない構造でデータを記述することがある データの定義に誤りがある場合でも、実際に適用してみるまで分からない マニフェストで定義するデータを使い回すことができず、再利用性が低い 複数のマニフェストで定義されたパラメータを一括で変更できない CUE言語は、これらの課題をどのように解決するのでしょうか。 Better YAMLとしてのCUE言語 CUE言語では、YAMLやJSONといった一般的なデータ記述言語と同じように、数値や文字列、リスト、辞書といった型を組み合わせて任意のデータを記述できます。 上記のKubernetesマニフェストをCUE言語で書く場合は、以下のようになります。 apiVersion: "apps/v1" kind: "Deployment" metadata: { name: "nginx-deployment" labels: app: "nginx" } spec: { replicas: 3 selector: matchLabels: app: "nginx" template: { metadata: labels: app: "nginx" spec: containers: [{ name: "nginx" image: "nginx:1.14.2" ports: [{ containerPort: 80 }] }] } } カッコを使ってデータ構造を表現しておりJSONに似た文法ではありますが、単一の値を定義するときは apiVersion: "apps/v1" のように { } を省略できます。 これにより、 selector: matchLabels: app: "nginx" といった形で、階層構造の深いデータを一行にまとめて表現することもできます。 YAMLと違ってカッコで辞書やリスト構造を表現しているため、インデントが無くてもデータの階層構造を正しく解釈できます。 また、利用者が記述するデータに対して型を指定することもできます。 以下のコードは上記のKubernetesマニフェストからPodのレプリカ数に関する定義を抜き出したものに、型による制約を追加したものになります。 spec: { replicas: int } spec: { replicas: 3 } 上記の例は、レプリカ数が整数であるという、型による制約を追加したものになります。 CUE言語によるデータの定義では、文字列や数値といった具体的な値とそのデータの型を同等のものとして記述でき、それによって型による制約が適用されます。 また、以下のように型による制約と並べて、具体的な値による制約を記述することもできます。 replicas: int replicas: >2 replicas: 3 CUE言語では cue というCLIが提供されており、 cue eval コマンドを用いてデータを評価することでその定義に矛盾がないか検証されます。 上記の例であれば、 cue eval コマンドを実行することで、いずれの制約も満たす具体的な値が出力されます。 replicas: 3 もし、以下のような矛盾した制約を追加した場合、 cue eval コマンドを実行することでエラーとして設定の誤りを検証できます。 replicas: int replicas: >5 replicas: 3 replicas: invalid value 3 (out of bound >5): ./intro.cue:2:11 ./intro.cue:3:11 CUE言語では、JSONやYAMLとの相互変換もサポートされています。 前述のCUE言語によるKubernetes Deploymentのマニフェストは cue import nginx-deployment.yaml というコマンドを利用し、YAMLで定義されたマニフェストをインポートして作成しています。 また、CUE言語で作成されたマニフェストも cue export nginx-deployment.cue --out yaml というコマンドでYAMLにエクスポートできます。 CUE言語による柔軟なデータ表現 この章では、CUE言語におけるいくつかの特徴的な機能に着目して、実際にKubernetesマニフェストを生成します。 以下の画像は、CUE言語を利用してプラットフォーム管理者とアプリケーション開発者が協力しながらKubernetesマニフェストを作成するシチュエーションをイメージしたフローチャートになります。 このフローチャートの流れに則り、Templating、Composite、API Schema Validationといった機能を確認しながらCUE言語のエッセンスに触れていきます。 Templating CUE言語では、テンプレートとなる内容を予め作成しておき、そのテンプレートに従って新たなデータを定義できます。 ここでは、以下のようにDeploymentのマニフェストを作成するためのテンプレートを定義してみます。 deployments: [_name=string]: { apiVersion: "apps/v1" kind: "Deployment" metadata: { name: _name + "-deployment" labels: app: _name } spec: { replicas: 3 selector: matchLabels: app: _name template: { metadata: labels: app: _name spec: containers: [{ name: _name image: string ports: [{ containerPort: int }] }] } } } このテンプレートの1行目の [_name=string] と記述している箇所で、テンプレート利用者が宣言したマニフェスト名を受け取って _name という変数にエイリアスしています。 この _name 変数は、テンプレート内で参照できます。 また、 metadata: name に _name 変数で指定された文字列と -deployment を結合した文字列を設定しています。 このテンプレートを利用してマニフェストを作成します。 以下の例では、1行目の deployments: nginx: という記述によりテンプレートの _name 変数に nginx を代入しています。 deployments: nginx: { spec: template: spec: containers: [{ image: "nginx:1.14.2" ports: [{ containerPort: 80 }] }] } このように定義されたデータは、以下のように -e オプションで出力する要素を指定して export コマンドを実行することで、YAMLとしてマニフェストを出力できます。 cue export templating.cue -e deployments.nginx --out yaml spec : ... containers : - name : nginx image : nginx:1.14.2 ports : - containerPort : 80 さらに一歩踏み込んで、テンプレートの中で変数を利用する例を見ていきます。 以下の例では、テンプレートの内容はほとんど同じですが、コンテナのイメージ名、ポート番号に変数を利用するよう変更を加えています。 deployments: [_name=string]: { _image: string _port: int apiVersion: "apps/v1" kind: "Deployment" metadata: { name: _name + "-deployment" labels: app: _name } spec: { replicas: 3 selector: matchLabels: app: _name template: { metadata: labels: app: _name spec: containers: [{ name: _name image: _image ports: [{ containerPort: _port }] }] } } } 今回は、テンプレートの定義の冒頭に _image と _port という変数を追加しています。 変数の先頭には _ を付けることで、 cue export コマンドを使用してマニフェストを出力する際に、これらの定義が出力されないようにしています。 この新しいテンプレートを使い、以下のようなアプリケーション用のマニフェストを記述することで、最小限の変数の指定からマニフェストを定義できます。 deployments: nginx: { _image: "nginx:1.14.2" _port: 80 } 変数で指定する値を変えるだけで、利用するイメージとポート番号が異なる別のアプリケーション用のマニフェストを定義することもできます。 deployments: postgres: { _image: "postgres:12.11" _port: 5432 } ここで仮に、アプリケーションをデプロイするプラットフォーム側の要望として Security Context の設定をマニフェストに追加したくなった場合を考えてみます。 以下のようにテンプレートの内容を一部変更してみます。 (前述のマニフェストと重複する箇所は省略しています。) deployments: [_name=string]: { _image: string _port: int ... spec: { ... template: { metadata: labels: app: _name spec: { securityContext: { runAsUser: 1000 runAsGroup: 3000 fsGroup: 2000 } containers: [{ name: _name image: _image ports: [{ containerPort: _port }] }] } } } } ここでは、マニフェストの spec の中に securityContext の設定を追加しました。 これにより、このテンプレートを利用しているアプリケーション側のマニフェストを変更することなく、最終的に出力されるDeploymentのマニフェストにも、以下のように securityContext の設定が反映されます。 spec : ... spec : securityContext : runAsUser : 1000 runAsGroup : 3000 fsGroup : 2000 containers : - name : nginx image : nginx:1.14.2 ports : - containerPort : 80 テンプレート機能を利用してマニフェストを記述することで、重複した構成を省略し、再利用が容易な形でデータを定義できます。 Composite CUE言語におけるTemplating機能を利用することで、自由に設定できる変数を事前に決めておき、それに従って値を埋めてマニフェストを作成できました。 ですが、実際にアプリケーションを開発していく中では、より自由に設定を追加したいことも多々あります。 そこで、CUE言語のComposite機能により値を設定する例を確認してみます。 以下の例は、テンプレート側の定義を変更せず、アプリケーション側の設定にコンテナで利用する環境変数を追加したデータ定義になります。 // create configuration with the template deployments: nginx: { _image: "nginx:1.14.2" _port: 80 } // composite extra configuration deployments: nginx: spec: template: spec: containers: [{ env: [{ name: "NGINX_HOST" value: "example.com" }] }] 今回はアプリケーション側の設定を変更することで、コンテナに環境変数の定義を追加しました。 このデータを利用することで、以下のように環境変数の設定を追加したマニフェストを出力できます。 spec : ... containers : - env : - name : NGINX_HOST value : example.com name : nginx image : nginx:1.14.2 ports : - containerPort : 80 上記の例のように、CUE言語ではテンプレート側で全ての設定を明示していない場合であっても、別のデータ定義と結合して新たな設定を追加できます。 テンプレートで宣言したデータと追加で宣言したデータは、任意の階層で結合されます。結合の順序に関係なく一貫した結果が生成されるため、テンプレートを使う定義と追加定義の順番を入れ替えても同じ結果が出力されます。 一方で、CUE言語の仕様として、一度定義された値を後から変更・削除できません。 例えば、テンプレートで定義された securicyContext を上書きして、 runAsUser を変えるようなマニフェストは宣言できません。 これにより、テンプレートで予め定義された設定内容を遵守しつつも、必要に応じて設定を追加するといった、堅牢さと柔軟さを兼ね備えたデータ定義が可能となります。 API Schema Validation これまでも設定の中で使ってきたように、CUE言語では int や string などの型を利用して、定義された値を型検査できます。 これにより、YAMLとしてマニフェストを生成するタイミングで無効な値が設定されていないか検証でき、マニフェストをKubernetesクラスタに適用する際の手戻りを減らすことができます。 その一方で、マニフェストで設定する全ての値の型情報を予め記述するのは非常に困難です。 このような課題に対し、CUE言語は、Go実装の構造体定義からCUE言語として評価可能な型定義を生成する機能を提供しています。 ここでは、マニフェストを作成するにあたり、事前にKubernetes APIの型定義を生成して、型検査に適用する方法を紹介します。 Kubernetes APIの型定義を取得するには、 Go言語 で利用される go と cue のCLIを利用して以下のコマンドを実行します。 go mod init example.com go get k8s.io/api/apps/v1 cue get go k8s.io/api/apps/v1 このコマンドを実行することで、カレントディレクトリに cue.mod ディレクトリが作成され、その中に型情報を定義したCUEファイルが保存されます。 ここで保存されるファイルは、Kubernetes APIを実装しているGo言語のソースコードから自動生成されたものになります。 KubernetesのDeploymentに関する型定義は k8s.io/api/apps/v1 というパッケージに #Deployment という名前で記述されているため、以下のようにマニフェストを修正して定義を参照できます。 import ( "k8s.io/api/apps/v1" ) deployments: [_name=string]: v1.#Deployment & { _image: string _port: int ... } 上記のマニフェストでは、5行目のテンプレートのデータ構造を定義する箇所で、 v1.#Deployment & という記述を追加しています。 この記述により、定義されたデータ構造が #Deployment として定義された型を利用して自動的に型検査されます。 例えば、コンテナの name を宣言し忘れたDeploymentマニフェストを生成しようとした際、 #Deloyment で定義された型に違反したことでCUEの評価が失敗します。 このように、実際のKubernetes APIを呼び出す前に、API schemaを使ってデータを検査できます。 ここで利用した型定義の出力機能は、Go言語のソースコードに対してだけでなく、 Protocol Buffers や OpenAPI でも利用できます。 この機能を活用することで、様々なAPIに対してCUE言語による型によって安全性が保証されたデータの定義を実現できます。 CUE言語とCloud Native Adapter ここまでの説明で、Templating、Composite、API Schema Validation といったCUE言語の特徴的な機能を確認してきました。 Qmonus Value Streamでは、CUE言語が提供されている Go API を活用して言語機能を拡張し、Cloud Native Adapterという独自のIaCを実装しています。 Qmonus Value Streamのユーザは、Cloud Native AdapterをCUE言語を用いて記述することで、自身のユースケースに合わせた「インフラストラクチャの構成」と「ワークフロー」を構築できます。 加えて、Qmonus Value Stream独自のComposite機能により、汎化されたCloud Native Adapterを自由に選択・結合することが可能となり、ユーザ間でのモジュールの再利用・プラクティスの共有を促します。 Cloud Native Adapterについては、 前回の記事 で解説していますので、興味があればそちらもご覧ください。 また、CUE言語のより詳細な情報については、以下のwebサイトも合わせてご覧ください。 Documentation | CUE (CUE言語の公式ドキュメント) Cuetorials おわりに DevOpsプラットフォームの取り組み連載の4回目の記事として、Qmonus Value Streamチームが利用しているCUE言語について紹介しました。 Qmonus Value Streamチームは、メンバ一人一人が、利用者であるアプリケーション開発者のために信念を持って活動を続けています。 プラットフォームを内製開発するポジション、プラットフォームを使ってNTTグループのクラウド基盤やCI/CDを構築支援するポジションそれぞれで 、一緒に働く仲間を募集していますので、興味を持たれた方は応募いただけると嬉しいです。 CI/CDプラットフォーム開発、ソフトウェアエンジニア NTTグループのクラウド基盤構築およびCI/CD導入、ソリューションアーキテクト 次回は、Qmonus Value StreamチームがCI/CDパイプラインを構築するために利用している Tekton について紹介します。お楽しみに! https://engineering.mercari.com/en/blog/entry/20220127-kubernetes-configuration-management-with-cue/ ↩
アバター
こんにちは。マネージド&セキュリティサービス部セキュリティサービス部門の閏間です。総合リスクマネジメントサービス「 WideAngle 」のサービス企画を担当しています。 皆さんは CISSP という資格をご存じでしょうか。CISSPとはCertified Information Systems Security Professionalの略で、セキュリティに関する国際的な認定資格です。去る6月23日に社内でCISSP資格保有者による小さな座談会を開催したのですが、本記事では、開催するに至った経緯や、開催してみてどうだったかなどについて紹介したいと思います。 勉強会開催の狙いは、部署でセキュリティ学習の機運を高めること 私が所属している部署は、企業向けにセキュリティサービスを開発して提供することをミッションとしています。メンバーの役割は色々あるので、必ずしも全員がセキュリティに関する深い知識を求められるわけではありません。しかし、部署全体でセキュリティに関する知識やスキルをより高めることは、業務遂行にプラスになるだろうと以前から感じていました。 そしてそのような状況を作り出すことに少しでも貢献できたらと思い、あるとき思い立って、以下のメッセージをチーム内のチャットに投稿してみました。 対象の資格としてCISSPを選んだ理由は2つあります。 CISSPは国際的な認定資格であり、セキュリティ分野を体系的に広くカバーしているため 私もCISSP資格保有者なので、勉強会の企画、運営だけでなく、情報を伝える面でも貢献できるため 投稿の結果は…"いいね"が複数ついたので、私の所属部署を対象に、勉強会を開催することを決意しました。 ちなみに開催を決意できた背景には、会社の自由な雰囲気というものがあります。NTT Comでは、ランチタイムの勉強会や、ときには夜間の勉強会など、有志による勉強会が盛んに行われています。このような状況は昨年私がNTT Comに中途入社して初めて知ったのですが、大企業なのにすごいと思った記憶があります。そうした光景を見て、自分もやっちゃえ、と思うことができました。 参加者の関心度を高めるために、身近な人による座談会形式にしました メンバーのCISSPに対する認識や考え方は人それぞれです。CISSPに関心がなかったり、関心があっても取得するつもりはなかったり、取得したいと思っているけど時期は決めていなかったり…。そのため、各人のCISSPに対する気持ちが一段階上げられれば、という想いで準備を進めることにしました。 工夫した点として、勉強会の形式は職場の資格保有者による座談会形式としました。資格保有者にパネリストとして体験談を語ってもらった後、パネリスト同士で会話したり、参加者から質問を受け付けたりするスタイルです。 座談会形式にしようと思った理由は次の通りです。 ネット上には有益なCISSPの勉強法や受験体験記はたくさんあり、今でもどんどん増え続けています(私も受検の際はかたっぱしから記事を読みましたが、学習計画を考える上でとても参考になったので、大変ありがたかったです)。ですが、そうした記事はCISSPに対する関心が薄い人には読まれないかもしれません。 そこで、身近な人に直接体験談を語ってもらうことで、興味を持って聞いてもらえるのではないか?と考えました。幸い私の周囲にはCISSP資格保有者が何人もいまして、3名の方に声をかけたところ、全員が協力依頼に快く応じてくれました。 なお、座談会当日の進行がスムーズにいくよう、パネリストの皆さんとは一度だけ事前ミーティングを実施しました。 その場で、「メンバーのスキル底上げにつながることに貢献したい」「カジュアルな雰囲気で楽しい会にしたい」という想いをパネリストの方にしっかり伝え、協力をお願いしました(カジュアルな雰囲気にしたいので、当日は顔出しお願いします、とも!)。 あとは以下のようなお題でエピソードを用意しておくことをお願いし、当日に向けて資料準備などを進めました。 当日は有意義な情報交換が行われました 座談会はリモート会議(Teams)で実施し、参加者数は40人弱でした。 ※NTT Comではリモートワーク実施率は80%を超えていますが、私の所属部署でも在宅で業務を行うことが多いため、今回の座談会に限らず、打ち合わせは基本的にはリモート会議で行っています。 アジェンダは以下の通りです。最初に私から簡単なCISSPの概要説明と、NTT ComのCISSP取得支援制度として受験費用や研修受講費に対する支援があることを紹介をしました。 そしてその後はメインである、パネリストによる体験談紹介です。体験談を語ってもらい、その後パネリスト同士で質問し合ったり、参加者からの質問やコメント(チャット or 音声)に答える形で進行させました。 なお、全員の体験談の紹介が終わった後に自由に質疑する時間をとりたかったのですが、体験談パートが盛り上がったため時間不足となり、残念ながら質疑パートはなしとなってしまいました。 パネリストとして当部署の事業の責任者の方にも参加してもらいましたが、その方からはNTT ComとCISSPの関わりについての昔話も聞けて興味深かったです。ISC2(CISSPの運営団体)の日本でのサービス開始は2004年ですが、そこにNTT Comが関わっており、関係したメンバーは必須に近い形でCISSPをとる必要があったそうです。 また、参加者からは、CISSP合格後の資格維持に必要となる継続教育(CPE;Continuing Professional Educationsと呼ばれています)について、どのようなことを行っているかとか、大変さなどについて、前のめりな質問ももらいました。 他にもパネリストの体験談や参加者からの質問はいろいろありました。主なものをご紹介します。 パネリストの体験談やアドバイス お客さまがCISSP資格保有者の場合にも対等な立場でディスカッションできるように、というのも取得を目指したひとつのきっかけ。 名刺にCISSPと記載されているだけで、とくに欧米のビジネスパーソンとは信頼関係を固く結べる。 公式問題集を繰り返し解くのが合格の近道。ただし問題と解答を覚えるのではなく、「なぜその解答が正しいのか」を理解するのが重要。 公式問題集を繰り返し解くには、 Anki というアプリを利用するのがお勧め(英単語の単語帳のような使い方ができるアプリで、忘却曲線に基づいて出題頻度を管理できる、暗記に適したツール)。 将来セキュリティ関連の業務を離れたとしても、形として残すことができるので資格取得するのはお勧めできる。 資格取得したことで自信がついた。その一方で専門家として見られるので、日々勉強しようという気持ちになる。 参加者からの質問やコメント CompTIA Security+との難易度の差は? 知識のアップデートは大事ですね。 Ankiや学習方法など、大変勉強になった。 ※ちなみにAnkiについては、座談会後に実際に使い始めた方もいます。 以上のように、座談会当日は活発な情報交換がなされ、とても有意義な会となりました。うれしいことに、座談会で交わされた話に触発されて学習にとりかかった、という方もいます。また、会社とCISSPの関わりの歴史を知れたとか、業務上関わりの薄かった人と準備を通して接することができたといった、企画側にとってうれしいこともありました。このように、座談会を開催したことは、参加者、企画側の双方に有益なものとなりました。 まとめ 社内で開催したCISSP座談会について、開催の経緯や、開催することで参加者、企画側の双方にメリットがあったということを紹介しました。一度座談会をやったくらいで何かが大きく変わることはないと思いますが、CISSP取得を目指す人が少しでも増えたらうれしいです。今後も継続的にこういった活動を行い、所属部署を盛り上げることに少しでも貢献できればと考えています。
アバター
目次 目次 はじめに 論文紹介 The Norm Must Go On: Dynamic Unsupervised Domain Adaptation by Normalization OcclusionFusion: Occlusion-aware Motion Estimation for Real-time Dynamic 3D Reconstruction EPro-PnP: Generalized End-to-End Probabilistic Perspective-N-Points for Monocular Object Pose Estimation Cascade Transformers for End-to-End Person Search TrackFormer: Multi-Object Tracking With Transformers Global Tracking Transformers TransWeather: Transformer-based Restoration of Images Degraded by Adverse Weather Conditions 最後に はじめに こんにちは、イノベーションセンターの鈴ヶ嶺・加藤・齋藤です。前編では、CVPR2022の概要とメンバーが参加したワークショップの模様をご紹介しました。後編では、CVPR2022本会議に採択された論文を7つご紹介します。 engineers.ntt.com 論文紹介 The Norm Must Go On: Dynamic Unsupervised Domain Adaptation by Normalization 1 実装: https://github.com/jmiemirza/DUA この論文では、ドメイン適応への応用を目的としたBatch Normalizationの改良が提案されています。 ドメイン適応(domain adaptation)には大量のラベルありorラベル無しデータが必要です。そのため、自動運転中の天候変化など、ドメインシフトが連続的に起こる環境ではデータが十分に収集できないことがあります。 この論文ではBatch Normalizationレイヤの統計量(running mean, running variance)をいじることで少量のラベル無しデータでのドメイン適応を実現しました。これにより、連続的なドメインシフトが起こる環境でもオンラインで適応させることが可能になります。 Dynamic Unsupervised Adaptation Batch Normalizationレイヤではミニバッチ内の平均と分散を移動平均を用いて記録し、データセット全体においてそのレイヤに入力されるデータの分布を正規化します。テスト時にはそれらの統計量は固定されますが、学習データの分布から離れた入力がくると出力分布が崩れてしまい、パフォーマンスが大きく劣化してしまいます。 そこでテスト時にもこれらの統計量(running mean, running variance)を更新することで、入力分布の変化に適応させます。 統計量はサンプルによって大きく変化しうるため、Batch Normalizationをそのまま適用するだけでは収束しにくいという問題がありました。この論文では、モーメンタムにexponential decayを適用することで、異なるドメインへの収束効率を向上させることが提案されています。 また、更新統計量を計算するときに左右反転、クロッピング、回転などのdata augmentationを施すことで性能をより良くできることが示されています。 所感 実装がシンプルで様々なタスクに利用できそうです。 一方で、ドメイン適応を自動化するためには統計量を監視しドメインシフトの発生を検出する必要がありそうです。 OcclusionFusion: Occlusion-aware Motion Estimation for Real-time Dynamic 3D Reconstruction 2 この論文では、RGB-Dカメラを入力とする、オクルージョンに強いリアルタイム3次元復元手法を提案しています。 LSTMにgraph neural networkを組み込み、今見えている場所の動きを蓄積することで見えていない部分の動きも予測するのが特徴です。ネットワークは物体表面をメッシュグラフで表現した時の各ノードの動きと予測自信度(予測分布の分散)を出力しています。 近年の3D復元はFusion-basedな手法が高い性能を誇っています。これは前景の物体を自動で追跡しながら3D復元できるため、対象物体の3Dモデルを必要とせず、また非剛体的な動き(タオルなど)も事前情報なしに予測できることが大きな要因です。 しかし単一視点の入力でオンライン処理をしようとすると、連続するフレーム間で信頼性のある対応点を取りづらく、誤差が蓄積されトラッキングに失敗することがあります。 本手法では、単一視点の困難の1つである「見えない部分のモーション予測」を、これまでの動きや物体形状による制約を活用することで高精度化し、オフライン手法をも超える予測精度を達成しました。 実行速度はNVIDIAのGeForce RTX 2080Ti x2, 解像度480pで36ミリ秒だとしています。 Network architecture 入力画像から2D optical flowを計算し、深度情報と併せて3D optical flowとします。これと対象物体のノードグラフを入力することで、各ノードに3D座標と(カメラから見えているなら)3Dモーションベクトルが特徴量として与えられます。この時あらかじめ物体全体の剛体運動(回転+平行移動の )を計算しておくことで、ネットワークは相対的な非剛体運動のみを推定させています。 そして各ノードにLSTMを導入してステートを持たせることで、これまでの動き情報も推論に活用できるようになっています。 学習時は予測値と予測分散に対してガウス分布のlog-likelihood lossを与えてネットワークを学習しています。 Confidence guided non-rigid reconstruction 前処理で使ったoptical flowやgraph neural networkの出力した予測モーションと確信度を使い、次のコスト関数の重み付き和を最小化するような各頂点のモーションを求めます。 深度制約:測定した深度と予測位置に一貫性を持たせる。前フレームの予測頂点と2D optical flowからその頂点の今フレームにおけるカメラ座標を計算し、その位置の深度情報をもとに3D空間に逆投影する。深度マップのサブピクセル分だけずれが生じるので、ピクセルをポリゴンとして3D空間に逆投影し、ポリゴンと予測頂点位置との距離をコストとする。 モーション制約:最終的な予測モーションとGraph neural networkの予測モーションとの一貫性を保つ。予測自信度で重み付けされたモーションベクトルとのユークリッド誤差をコストとする。 2D制約:最終的な予測モーションと2D optical flowとの一貫性を保つ。物体の各頂点をカメラ座標に投影したときの2次元モーションと2D optical flowとのユークリッド誤差をとる。 剛体制約:物体の硬さを制約にする。隣接する頂点同士に対してお互いの予測モーションベクトルのユークリッド誤差をとる。 これで最適化したのちにモデルの最終的な形状を決定します。新しい頂点をトラッキングに追加する方法はDynamicFusion 3 を利用しています。 EPro-PnP: Generalized End-to-End Probabilistic Perspective-N-Points for Monocular Object Pose Estimation 4 PnP問題のエンドツーエンド確率推論を実現する手法であるEPro-PnPが提案されています。 エンドツーエンドの確率的PnP 以下が全体のアーキテクチャです。注目するポイントは 学習時にEPro-PNPで事後位置姿勢分布 を導出している点です。最終的には、特殊ユークリッド群SE(3)多様体上の位置姿勢の分布を連続空間に対応可能なSoftmaxを用いて出力します。推論時にはソルバーを用いて位置姿勢 を推定しています。 PnP問題の最終的な目的は の2D, 3DのN点の対応点を求めることです。それを定式化すると式(1)のように最小化する関数が導けます。 は回転ベクトル、 は並進ベクトル、 はカメラの内部キャリブレーションのパラメーターを含めた射影関数、 は要素積を示しています。 また、 は3Dから2Dに再射影した誤差を表しています。 式(1)は非線型最小問題のため一意な解でない可能性があり扱いづらいので、エンドツーエンド学習のために微分可能な確率密度を出力するPnPをモデル化しています。累積誤差は次のように定義される尤度関数の負の対数として考えています。 事前位置姿勢分布 を用いてベイズの定理より事後位置姿勢分布 は次のように導出されます。 式(3)は連続空間に対応可能なSoftmaxとして解釈可能です。 学習時には次の式ように教師位置姿勢分布 と のKLダイバージェンスを最小化することで学習します。 式(2)を代入すると以下のような簡略化した損失関数が定義できます。 の積分についてはGPUで計算しやすいK点のサンプルから計算するAMIS(Adaptive Multiple Importance Sampling) 5 という効率的なモンテカルロ手法を利用します。 不確実性と識別のバランス 損失関数に対する重み の勾配は次の式(8)ように導出されます。ここで非加重再投影誤差を として扱います。 最初の項の は負の符号であり再射影誤差が大きく、つまり不確かさが大きい対応点ほど重みづけをしないことを示しています。 次の項の は予測された姿勢に対する再射影の分散を示しています。また正の符号であるため感度の高い対応点にはより強く位置姿勢判別ができるためより重みづけをする必要があることを示しています。 最終的な勾配は図3のようなものとなります。このように学習された対応点の重みは、逆不確実性と識別性に分解できます。既存の研究では前者のみを考慮するため識別性能が不足していました。 実験結果 表4ではnuScenes Benchmark 6 を用いた結果を示しています。ここでは3つのEPro-PnPを評価しています。 EPro-PnP 座標回帰損失 7 を追加したもの test-time flip augmentation (TTA) 8 をさらに追加したもの EPro-PnPはベースラインのFCOS3Dを大きく上回っています(nuScenes Detection Score (NDS) 0.425 vs 0.372)。この結果から大規模なデータセットに適切なエンドツーエンドのパイプラインを設定することで直接の位置姿勢推定を上回る結果となることを示唆しています。また、ポーズ推定精度を反映するmAOEについても基本的に他の手法よりもEPro-PnPが上回っています(mAOE 0.337 vs. 0.36)。TTAを用いた結果ではthe state of the artを上回る結果を示しています(NDS 0.439 vs. 0.422)。 定性的な結果として図7で既存手法のCDPN 9 と学習した重みを比べると提案手法はあまりエッジ部分の詳細をキャプチャできていないです。また、提案手法の座標マップはCDPN 79.46 よりも 79.96 となっているため性能が良いことが分かります。 実際に動作させた結果 以下に実際に動作させた例を記載します。 以下で公開されているpretrain modelの中から今回は epropnp_det_v1b_220411.pth を利用しました。 https://drive.google.com/drive/folders/1AWRg09fkt66I8rgrp33Lwb9l6-D6Gjrg export PATH= /usr/ local /cuda-11. 3 /bin: $PATH export LD_LIBRARY_PATH= /usr/ local /cuda-11. 3 /lib64: $LD_LIBRARY_PATH conda create -y -n epropnp_det python = 3 . 7 conda activate epropnp_det conda install -y pip pip install torch = = 1 . 10 . 1 +cu113 torchvision = = 0 . 11 . 2 +cu113 torchaudio = = 0 . 10 . 1 -f https://download.pytorch.org/whl/torch_stable.html pip install mmcv-full == 1 . 4 . 1 -f https://download.openmmlab.com/mmcv/dist/cu113/torch1. 10 . 0 /index.html conda install -y -c fvcore -c iopath -c conda-forge -c bottler fvcore iopath nvidiacub git clone https://github.com/facebookresearch/pytorch3d cd pytorch3d && git checkout v0. 6 . 1 && pip install -v -e . && cd .. git clone https://github.com/tjiiv-cprg/EPro-PnP.git cd EPro-PnP/EPro-PnP-Det pip install -v -e . # sample image inference python demo/infer_imgs.py demo/ configs/epropnp_det_v1b_220411.py epropnp_det_v1b_220411.pth --show-views 3d bev mc # my image inference cp -r demo mydemo # setup my image(file path: mydemo/demo.png) vim mydemo/nus_cam_front.csv # edit camera intrinsic matrix python mydemo/infer_imgs.py mydemo/ configs/epropnp_det_v1b_220411.py epropnp_det_v1b_220411.pth --intrinsic mydemo/nus_cam_front.csv --show-views 3d bev mc 次の結果は、MIT DriveSegのデータセット 10 の画像を推論した結果になります。 右奥の車両の検出はされていませんが、確率的に存在する可能性の高さを2Dのマップ画像から確認できます。また、奥のいる人などの位置関係はおおむね適切にマッピングされていることが分かります。 所感 微分が難しいPnP問題に対して確率的なレイヤーを加えて柔軟性を持たせることでエンドツーエンド学習が可能となる点が興味深かったです。また、この仕組みは既存のPnP推定ネットワークにも広く応用できるので応用力も期待できると感じました。 Cascade Transformers for End-to-End Person Search 11 画像データベース(gallery set)からクエリ画像に映る特定の人物を検索するperson searchタスクに対し、 この論文ではCascade Occluded Attention Transformer (COAT) を提案し、E2Eな人物検索を実現します。 実装: https://github.com/Kitware/COAT Person search 人物検索タスクに対する一般的なアプローチは、大きく(1)人物検出、(2)特徴量抽出、(3)特徴量の比較に基づくgallery画像のランキング生成から構成されます。(2)と(3)は、所謂 person re-identification(ReID)と同一です。 このperson searchタスクでは次のような困難が挙げられます。 人物検出とReIDそれぞれで必要とする特徴量の性質が異なる 人物検出ではどんな見た目の人間でも検出できるような汎用性のある特徴量を学習する必要があるのに対し、ReIDでは個人ごとに分別可能な特徴量を必要とするため、単一の特徴量抽出器で両方をこなすのは難しい。 E2Eモデルでは検出用とReID用のどちらを先に特徴抽出するかでモデルの構造が変わる。 ReID first: 多様性のある特徴量マップを計算したのち、人間にあたる特徴を検出する detection first: 人間を検出するネットワークでRoI(region of interest)を列挙し、それぞれのbounding boxからReID用特徴を計算する 多様なサンプルに対応する必要がある scale variations. 写っている人物のサイズが極端に異なる pose/viewpoint change. 写っている人物の向きや姿勢が異なる occlusion. 体が別の人や障害物に隠れている 人物検出器 本研究では物体検出器としてCascade R-CNN 12 を適用しています。小さなIoU閾値で学習させる弱い検出器から得たbounding boxを、より厳しいIoU閾値で学習させる検出器のproposalに使い、さらにそこから得たbounding boxをもっと強い検出器のproposalに...という流れでbounding boxを段階的に洗練させるのが特徴です。 これはfaster R-CNNにおけるRegion proposal network (RPN) を複数段に拡張したものといえます。 Cascade Occluded Attention Transformer 本手法の提案アーキテクチャです。オクルージョンに強い特徴量が得られるOccluded Attention Transformerを多段に繋ぎ、前ステージで推測したbounding boxを次のステージのproposalに使い、そこから推測したbounding boxをさらに次のステージのproposalに...という流れで段階的に人物検出とReID特徴を洗練させます。 本手法ではTransformerへの入力前に以下のような工夫を施しています。 マルチスケール特徴を入力とする。各proposalの特徴マップをチャンネル方向にいくつか分割したのち、convolution layerで特定のスケールに縮小する。そうして得られたマルチスケール特徴をtokenとしてtransformerの入力に使う。 Data augmentationでオクルージョンをシミュレートする。具体的にはCutMix 13 の要領で一部のtokenをバッチ間でシャッフルする(切り出す座標と貼り付ける座標は同一)。これにより、各サンプルが一部別の物体によって隠れることを再現できる。学習時のみ有効。 評価 CUHK-SYSU 14 , PRW 15 datasetで評価し、sotaまたはそれに近い性能を達成しました。また、cascadeの各ステージで人物検出の正解判定に用いるIoU閾値は段階的に増やすことで性能が良くなること、また本手法のoccluded attention transformerは、他のattention機構やcutout/mixup等のdata augmentationを使うよりも高い性能を出すことが示されています。 推論速度はNVIDIA A100 GPU, Pytorch実装, 入力サイズ900x1500で11.14FPSでした。 TrackFormer: Multi-Object Tracking With Transformers 16 この論文では、Tracking-by-attentionという新しいトラッキングパラダイムに基づく、エンドツーエンド学習可能なMOT手法を提案しています。 Tracking-by-attention MOTでは、Tracking-by-detectionと呼ばれる物体検出と検出結果のフレーム間対応付けをカスケードに行う手法が一般的です。それに対して、この提案法ではフレーム特徴と物体・追跡クエリに対するAttentionを基にフレーム間の関連付けを行い物体追跡を実現するTracking-by-attentionを提案しています。物体クエリはバウンディングボックスなどの物体検出に利用される埋め込みベクトルであり、追跡クエリは自己回帰的に移動する位置に対応しフレーム間のオブジェクトを追跡しそのIDを引き継ぐ埋め込みベクトルです。また物体・追跡クエリは連結して利用されるため、この提案法は検出と追跡を同時に出力できます。 MOTを集合予測問題として定式化 提案するモデルは図2のように、以下の連続したステップでフレームごとの物体のバウンディングボックスとIDに関するクラスを予測する集合予測問題として定式化しています。 Res-Net-50 17 などの一般的なCNN backboneを用いて 時点の入力からフレーム特徴を抽出する Transformerエンコーダ 18 のself-attentionでフレーム特徴をエンコードする Transformerデコーダで 時点の物体・追跡クエリ用いてMLPでバウンディングボックスとクラス予測にマッピングする 実験結果 表1では、MOT17 19 のデータセットを用いて評価した結果で優れた精度を示していることが分かります。 図3は、MOTS20 20 データセットを用いてR-CNNと比較した結果です。提案法のセグメンテーションの性能が優れていることが分かります。 実際に動作させた結果 以下に実際に動作させた例を記載します。 conda create --name trackformer python = 3 . 7 cmake -y conda activate trackformer conda install pytorch = = 1 . 5 . 0 torchvision = = 0 . 6 . 0 -c pytorch git clone https://github.com/timmeinhardt/trackformer.git cd trackformer pip install -r requirements.txt pip install -U ' git+https://github.com/timmeinhardt/cocoapi.git#subdirectory=PythonAPI ' python src/trackformer/models/ops/setup.py build --build-base=src/trackformer/models/ops/ install cd models wget https://vision.in.tum.de/webshare/u/meinhard/trackformer_models_v1.zip unzip trackformer_models_v1.zip cd ../ # sample video inference ffmpeg -i data/snakeboard/snakeboard.mp4 -vf fps = 30 data/snakeboard/%06d.png python src/track.py with dataset_name =DEMO data_root_dir =data/snakeboard output_dir =data/snakeboard write_images =pretty # my video inference # setup my video(file path: data/myvideo/myvideo.mp4) ffmpeg -i data/myvideo/myvideo.mp4 -vf fps = 30 data/myvideo/%06d.png python src/track.py with dataset_name =DEMO data_root_dir =data/myvideo output_dir =data/myvideo write_images =pretty 次の結果は、DanceTrack 21 の映像に本手法を適用した結果になります。 所感 Tracking-by-attentionによりエンドツーエンドでトラッキングを可能としている点が興味深く、今後のこのような手法に期待させられるような内容でした。 Global Tracking Transformers 22 Transformerベースのマルチオブジェクトトラッキング手法を提案している論文です。 Local tracker と Global tracker 主に従来のトラッキング手法ではフレームごとに物体を関連づけるLocal trackersという手法が用いられていました。この提案手法では、短い動画クリップを入力として全てのフレームを通して物体を関連づけるGlobal trackerが用いられています。 Global tracking transformers(GTR) この提案法は上記のように最初に、全フレームから独立して物体を検出しGlobal tracking transformers(GTR)のエンコーダーに入力されます。またGTRは軌跡クエリをデコーダーの入力として各クエリと物体の関連性スコアを出力し関連づけます。 評価結果 表1にGTRの異なる時間窓の性能をTAO 23 、MOT17のデータセットを用いて評価しています。基本的な傾向としてより長い時間窓では性能が向上しています。 表3の結果はMOT17のテストデータを用いた結果です。この結果からGTRはトップクラスの性能を達成していることが分かります。 実際に動作させた結果 以下に実際に動作させた例を記載します。 conda create --name gtr python = 3 . 8 -y conda activate gtr conda install pytorch torchvision torchaudio cudatoolkit = 11 . 1 -c pytorch-lts -c nvidia pip install " git+https://github.com/facebookresearch/detectron2.git " git clone https://github.com/xingyizhou/GTR.git --recursive cd GTR pip install -r requirements.txt # setup pretrain model(file path: models/GTR_TAO_DR2101.pth) # https://drive.google.com/file/d/1TqkLpFZvOMY5HTTaAWz25RxtLHdzQ-CD/view?usp=sharing # sample video inference python demo.py --config-file configs/GTR_TAO_DR2101.yaml --video-input docs/yfcc_v_acef1cb6d38c2beab6e69e266e234f.mp4 --output output/demo_yfcc.mp4 --opts MODEL.WEIGHTS models/GTR_TAO_DR2101.pth # my video inference # setup my video(file path: myvideo.mp4) python demo.py --config-file configs/GTR_TAO_DR2101.yaml --video-input myvideo.mp4 --output output/demo_myvideo.mp4 --opts MODEL.WEIGHTS models/GTR_TAO_DR2101.pth 次の結果は、DanceTrackの映像に本手法を適用した結果になります。 非常にオクルージョンが多いデータとなっており、IDの切り替わりなどが多少見受けられる結果になりました。 所感 フレームごとにトラッキングするのではなく、グローバルにトラッキングするという方針が興味深かったです。また、現状GPUメモリの制約があるため実装上では時間窓を32に設定するのが限界のようですが、今後のGPU性能がスケールした際の性能向上に期待が持てると思いました。 TransWeather: Transformer-based Restoration of Images Degraded by Adverse Weather Conditions 24 TransWeatherとは カメラに映り込む悪天候時の雨粒、霧、雪片を除去するために特定のネットワークの構築や複数のEncoder-Decoderのネットワークを構築していましたが、パラメータ数が大きくなる問題がありました。そこで、TransWeatherでは、単一のEncoder-Decoderのネットワークで雨、霧、雪の除去できるネットワークを構築し、特定の状況に特化したモデルと同程度もしくは良い結果を出しています。 ネットワークの説明 ネットワークの構成は、単一のEncoder-Decoderネットワークであり、シンプルな構成となっています。TransWeatherのEncoderは、主に3つの構成要素があります。 図1の左から見ていくと、Intra Patch Transformerは、雨、霧、雪などの細かいものをとらえるために、Patchを縦横半分のサイズでconcatして入力画像として渡しています。Transformer blockでは、計算量を抑えるためにダウンサンプリングの工夫を凝らしているPyramid Vision Transformerを採用しています。TransformerのFFNの層では、Pyramid Vision TransformerのDWConvとMLPの層を設けている構成となっています。 Decoder部分では、通常のViTではdecoderの入力(Q,K,V)は、encoderの最終層から出力された特徴量を使いますが、TransWeatherはクエリ(Q)に天候を入力し、KとVについてencoderの最終層から出力された特徴量を入力としています。 クエリ(Q)に天候を入力している部分の実装 を追ってみると、ランダムな1×48のTensorを設定しています。体感としてはここは雨、雪、霧の状態を表すTensorを想定していたので、クエリがこのTensorで妥当なのか、私は実装からは読み取ることができませんでした。 self.task_query = nn.Parameter(torch.randn(1,48,dim)) TransfromerのEncoderとDecoderから出力された特徴量を、MSBDN 25 のDecoderに渡し、Dehazeの処理をします。それにより、クリーンな画像が出力されます。 Figure2. TransWeatherのネットワークアーキテクチャ図 実験結果 学習のデータセットはAll in One 26 で使用された学習のデータセットと同じものを使っています。Snow100K 27 の9,000画像、Raindrop 28 の1,069画像、Outdoor-Rain 29 の9,000画像を利用しています。 テスト画像には、Test1、RainDrop、Snow100k-Lを使用しています。 Table1. 既存のモデルとの比較 評価指標 - PSNR 2枚の画像で同じ位置同士のピクセルの輝度の差分の2乗を計算した指標です。 - SSIM 輝度、コントラスト、構造を軸にして周囲のピクセル平均、分散、共分散をとることで、ピクセル単体のみならず、周囲のピクセルとの相関を取り込んだ指標となっています。 この評価指標においてTransWeatherは、PSNR、SSIMどちらにおいても既存のモデルを上回っているということを述べています。 パラメータ数も減少しており、Transweatherはパラメータ数が31Mであり、All in One Networkのパラメータ数44Mと比較するとより少なくなっています。 Figure4. モデルごとのDehazeした結果比較 動作結果 コードを動作させるためにはutil.pyが必要なのですが、github上に実装がなかったためSyn2Realのutil.pyを引用しました。学習済みモデルは見つけることができませんでした。 そのため、配布されている学習セットを使用して手元で学習させました。 # このままcloneしただけでは動かない git clone https://github.com/jeya-maria-jose/TransWeather.git cd TransWeather # NVIDIA RTX3090を用いたのでcudaバージョンはcuda11.0を使用 # torch 1.8.0以上だとsixがないため動かないので、今回はtorch1.7.1をインストール conda env create -f environment.yml conda activate transweather pip install torch1. 11 . 0 +cu113 torchvision0. 12 . 0 +cu113 torchaudio0. 11 . 0 --extra-index-url https://download.pytorch.org/whl/cu113 pip install mmcv-full1. 5 . 3 -f https://download.openmmlab.com/mmcv/dist/cu111/torch1. 10 . 0 /index.html # epoch数はデフォルトの250だったためそのまま採用 python train.py -train_batch_size 32 -exp_name Transweather -epoch_start 0 -num_epochs 250 python test_test1.py -exp_name TransWeather 論文では、Test1、RainDrop、Snow100k-Lを利用していると述べていました。そこに加えcityscapes dataset(leftimg8bit_trainval_rain)とドメインが全く異なるiPoneで撮影した画像を使用してテストを行いました。snow100k、test1、RainDropでテスト画像を何枚使用しているかは不明であったため、論文のテストセットとは同一ではないと思われます。 Table2. 論文での実験結果 Table3. 手元のテスト結果 表1と表2を比較すると、psnrとssimの評価指標が論文中の実験と比較し、下回っているように思えます。 次に、1024×576のiPhoneで撮影した画像に対して定性的な評価をしていきます。入力画像を上に、dehazeが行われた後画像の出力画像を下に載せます。赤枠で囲んだ部分を見ると、小さな雪片がうまく消せていることから、学習がうまくいき、Dehazeができていることがわかります。ただ、大きな雪片については、Dehazeが一部できていないように見えます。 雨天の入力をした場合においても、赤枠の部分を注目すると両方の画像において、入力画像にたいして出力画像が雨粒を一部消せていることがわかります。 所感 TransWeatherは単一のEncoder-Decoderネットワークを使用していることからAll in Oneの手法で明確な違いがあることやIntra-patchの導入をしました。それが、既存のモデルを評価指標の面に置いて良い結果につながっていること示しています。公開コードの学習推論を一通り実行することはできたものの、テストセットの詳細が不明なため論文に示されてる結果の再現実験は困難な印象を持ちました。また、これは本手法に限りませんが、dehazeなどの処理が悪天候時の映像を入力とした物体認識にどの程度効果があるのかについても調査してみたいと考えています。 最後に 本ブログでは、CVPR2022への参加者が興味を持った論文をご紹介しました。 NTT Comでは、今回ご紹介した論文調査、画像や映像、更には音声言語も含めた様々なメディアAI技術の研究開発に今後も積極的に取り組んでいきます。 アカデミックな研究に注力したくさん論文を書きたい 最新の技術をいちはやく取り入れ実用化に結び付けたい AIアルゴリズムに加え、AI/MLシステム全体の最適な設計を模索したい という方々に活躍していただけるフィールドがNTT Comには多くあります。今後も私たちの取り組みをブログ等で発信していきますので、興味を持ってくださった方は是非今後もご注目ください! M. Jehanzeb Mirza, Jakub Micorek, Horst Possegger, Horst Bischof. The Norm Must Go On: Dynamic Unsupervised Domain Adaptation by Normalization, In CVPR, 2022. ↩ Wenbin Lin, Chengwei Zheng, Jun-Hai Yong, Feng Xu. OcclusionFusion: Occlusion-aware Motion Estimation for Real-time Dynamic 3D Reconstruction. In CVPR, 2022. ↩ Richard A. Newcombe, Dieter Fox, Steven M. Seitz. DynamicFusion: Reconstruction and Tracking of Non-rigid Scenes in Real-Time. In CVPR, 2015. ↩ Chen, Hansheng et al. “EPro-PnP: Generalized End-to-End Probabilistic Perspective-N-Points for Monocular Object Pose Estimation.” Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR). 2022. ↩ Jean-Marie Cornuet, Jean-Michel Marin, Antonietta Mira, and Christian P. Robert. Adaptive multiple importance sampling. Scandinavian Journal of Statistics, 39(4):798–812, 2012. 3 ↩ Holger Caesar, Varun Bankiti, Alex H. Lang, Sourabh Vora, Venice Erin Liong, Qiang Xu, Anush Krishnan, Yu Pan, Giancarlo Baldan, and Oscar Beijbom. nuscenes: A multimodal dataset for autonomous driving. In CVPR, 2020. 1, 5, 6 ↩ Hansheng Chen, Yuyao Huang, Wei Tian, Zhong Gao, and Lu Xiong. Monorun: Monocular 3d object detection by reconstruction and uncertainty propagation. In CVPR, 2021. 2, 3, 4, 5, 6, 7, 8 ↩ Tai Wang, Xinge Zhu, Jiangmiao Pang, and Dahua Lin. FCOS3D: Fully convolutional one-stage monocular 3d object detection. In ICCV Workshops, 2021. 5, 6, 7, 8 ↩ Zhigang Li, Gu Wang, and Xiangyang Ji. Cdpn: Coordinates-based disentangled pose network for real-time rgb-based 6-dof object pose estimation. In ICCV, 2019. 1, 2, 5, 6, 7, 8 ↩ Ding, Li et al. “MIT DriveSeg (Manual) Dataset for Dynamic Driving Scene Segmentation.” (2020). ↩ Rui Yu, Dawei Du, Rodney LaLonde, Daniel Davila, Christopher Funk, Anthony Hoogs, Brian Clipp. Cascade Transformers for End-to-End Person Search. In CVPR, 2022. ↩ Zhaowei Cai, Nuno Vasconcelos. Cascade R-CNN: delving into high quality object detection. In CVPR, 2018. ↩ Sangdoo Yun, Dongyoon Han, Seong Joon Oh, Sanghyuk Chun, Junsuk Choe, Youngjoon Yoo. CutMix: Regularization Strategy to Train Strong Classifiers With Localizable Features. In ICCV, 2019. ↩ Tong Xiao, Shuang Li, Bochao Wang, Liang Lin, Xiaogang Wang. Joint detection and identification feature learning for person search. In CVPR, 2017. ↩ Liang Zheng, Hengheng Zhang, Shaoyan Sun, Manmohan Chandraker, Yi Yang, Qi Tian. Person re-identification in the wild. In CVPR, 2017. ↩ Meinhardt, Tim et al. “TrackFormer: Multi-Object Tracking With Transformers.” Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR). 2022. ↩ Kaiming He, Xiangyu Zhang, Shaoqing Ren, and Jian Sun. Deep residual learning for image recognition. In IEEE Conf. Comput. Vis. Pattern Recog., 2016. 2, 3, 6 ↩ Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N Gomez, Ł ukasz Kaiser, and Illia Polosukhin. Attention is all you need. In Adv. Neural Inform. Process. Syst., 2017. 1, 2, 3, 4, 9 ↩ A. Milan, L. Leal-Taixe, I. Reid, S. Roth, and K. ´ Schindler. MOT16: A benchmark for multi-object tracking. arXiv:1603.00831, 2016. 2, 3, 5, 6 ↩ Paul Voigtlaender, Michael Krause, Aljosa Osep, Jonathon Luiten, Berin Balachandar Gnana Sekar, Andreas Geiger, and Bastian Leibe. Mots: Multi-object tracking and segmentation. In IEEE Conf. Comput. Vis. Pattern Recog., 2019. 2, 6, 7, 8 ↩ Sun, Peize et al. “DanceTrack: Multi-Object Tracking in Uniform Appearance and Diverse Motion.” Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR). 2022. ↩ Zhou, Xingyi et al. “Global Tracking Transformers.” Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR). 2022. ↩ Achal Dave, Tarasha Khurana, Pavel Tokmakov, Cordelia Schmid, and Deva Ramanan. Tao: A large-scale benchmark for tracking any object. In ECCV, 2020. 3, 4, 5, 6, 7 ↩ Jeya Maria Jose Valanarasu, Rajeev Yasarla, Vishal M. Patel.TransWeather: Transformer-based Restoration of Images Degraded by Adverse Weather Conditions. In CVRP2022 ↩ Hang Dong, Jinshan Pan, Lei Xiang, Zhe Hu, Xinyi Zhang, Fei Wang, and Ming-Hsuan Yang. Multi-scale boosted dehazing network with dense feature fusion. In Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition, pages 2157–2167, 2020 ↩ Ruoteng Li, Robby T. Tan, Loong-Fah Cheong1.All in One Bad Weather Removal using Architectural Search. In CVRP2020 ↩ Yun-Fu Liu, Da-Wei Jaw, Shih-Chia Huang, and Jenq-Neng Hwang. Desnownet: Context-aware deep network for snow removal. CoRR, abs/1708.04512, 2017. ↩ Rui Qian, Robby T Tan, Wenhan Yang, Jiajun Su, and Jiaying Liu. Attentive generative adversarial network for raindrop removal from a single image. In Proceedings of the IEEE conference on computer vision and pattern recognition, pages 2482–2491, 2018. ↩ Ruoteng Li, Loong-Fah Cheong, and Robby T Tan. Heavy rain image restoration: Integrating physics model and conditional adversarial learning. In Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition, pages 1633–1642, 2019 ↩
アバター
目次 目次 はじめに CVPR2022概要 Workshop on Image Matching: Local Features & Beyond SuperPoint and SuperGlue: Lessons Learned Large-scale 3D reconstruction Deployment - Successes, Challenges, Open Problems Unstructured Object Matching using Co-Salient Region Segmentation Nerfels: Renderable Neural Codes for Improved Camera Pose Estimation Feature Query Networks: Neural Surface Description for Camera Pose Refinement Learning Co-segmentation by segment swapping for retrieval and discovery DA-AE: Disparity-Alleviation Auto-Encoder Towards Categorization of Heritage Images for Aggrandized 3D Reconstruction Detecting and Suppressing Marine Snow for Underwater Visual SLAM A case for using rotation invariant features in state of the art feature matchers Kaggle IMC 2022 感想戦 1st place solution 5th place solution 7th place solution 最後に はじめに こんにちは、イノベーションセンターの鈴ヶ嶺・加藤・齋藤です。普段はコンピュータビジョンの技術開発やAI/MLシステムの検証に取り組んでいます。6月19日から24日にかけて、コンピュータービジョン分野におけるトップカンファレンスのひとつである CVPR2022 がオンラインで開催され、NTT Comからは複数名オンラインで参加しました。その参加レポートを前後編に分けて紹介します。 前編では会議の概要とワークショップ、Kaggle IMC 2022 感想戦について紹介します。後編では、論文の紹介をしたいと思います。 CVPR2022概要 CVPRは毎年6月に開催されるコンピュータービジョン分野におけるトップカンファレンスのひとつです。今年は現地(New Orlens)とオンラインのハイブリッド開催でした。採択率は25.3%(2064/8161)となっており、昨年(採択率1661/7093=23.6%)同様当該分野に高い関心が寄せられていることが分かります。以下の図のように国別の採択割合で見ると、昨年に引き続き中国、次いでアメリカ、韓国が割合として多くを占めています。 以下は、分野別の採択率を示したグラフです。3D from multi-view and sensors、Pose estimation and tracking分野の採択率が多い傾向にありました。 オープニングメッセージ から引用 採択論文のタイトルをワードクラウドで可視化すると以下の図が得られます。Image、Video、Learningといったある意味自明なキーワードに加え、Transformerや3Dなどの出現頻度も高いことが見て取れます。 Workshop on Image Matching: Local Features & Beyond https://image-matching-workshop.github.io ここからワークショップについての紹介になります。 このワークショップでは、カメラ姿勢の推定や3次元再構成の基礎となる画像マッチング技術を取り扱っています。 これに合わせてKaggleでは画像マッチングのコンペティション(Kaggle IMC 2022)が開催され、ワークショップ終盤では上位陣のアプローチが紹介されました。 この章では各発表の概要を紹介していきます。 SuperPoint and SuperGlue: Lessons Learned by Tomasz Malisiewicz, Meta Reality Labs この発表ではDeep Learningベースの画像マッチング手法であるSuperPoint(CVPR workshop 2018)およびSuperGlue(CVPR 2020)の概要と、その開発の過程で得られた教訓が紹介されました。 SuperPoint: Self-Supervised Interest Point Detection and Description 1 SuperPointとは、Visual SLAMのフロントエンド(特徴点抽出)に使われる畳み込みニューラルネットです。 Fully Convolutional Networkなので、計算の途中で特徴点周りの画像パッチを切り出すようなことはせず、特徴点の検出と記述子の計算が同時に行われることが特徴です。 記述子はSiamese trainingによるmetric learningで学習する一方、特徴点検出の学習や、記述子の学習に必要な点対応の生成はself-supervised learningでおこなっています。 self-supervised learningでは、まず特徴点が自明な人工データ(単純な立体のCG画像)で学習し、これによって得たモデルにMSCOCOデータを入力しそれらの擬似ラベルを生成します。そして画像をランダムに変形することで、特徴点とその対応関係が分かっている画像ペアが手に入ります。 これを教師データとみなしてSuperPointの全体を学習できます。 画像の変形はあくまで擬似的な変形であり、3次元空間でのカメラ移動を再現できませんが、実データでの評価により3次元的な画像の変化に対しても汎化できていることがわかっています。 SuperPoint開発過程で得られた教訓 畳み込みニューラルネットワークで直接6-DoF relative poseを推定させたがうまくいかなかった。 Object detectionの技法が点検出に役立った。 in-houseなプライベートデータセットではなくMSCOCOを使うという決断は、汎化性能を向上するという意味でもオープンソースの精神という意味でも有利に働いた。 人工データセットの構築は学習レシピをシンプルにしてくれた。 SuperGlue: Learning Feature Matching with Graph Neural Networks 2 SuperGlueは、特徴点とその記述子をグラフニューラルネットワークに入れて、self-attention(同一画像内の点同士の関係をエンコード)とcross-attention(異なる画像内の点同士の関係をエンコード)モジュールを利用しマッチング用の特徴量を計算します。 各点の特徴量を計算した後はお互いの類似度を計算し、Sinkhorn Algorithmによって最終的な対応関係を算出します。このソルバは微分可能なので、学習時の勾配を前段のグラフニューラルネットワークまで伝播させることができます。 記述子にSIFT、SuperPointどちらを用いた場合でも、SuperGlueによるマッチング抽出は高い性能を発揮します。 SuperGlue開発過程で得られた教訓 実用システムから離れるという決断。記述子から特徴量を再計算するのはあまり実用的でないが、新しい手法の提案につながった。 Q&A 特徴点対応の算出を経由しないポーズ推定は今後可能になるだろうか? 昔ながらのCNNでは無理かもしれない。実際SuperPointの開発過程でやってみたがうまくいかなかった。しかし、将来的に新しいテクニック(ViTとか)で可能になるかもしれない。 Large-scale 3D reconstruction Deployment - Successes, Challenges, Open Problems by Ales Hrabalik, Capturing Reality/Epic Games この講演では、3次元再構成のアプリケーション、現在の技術のlimitationとその対策、未解決問題について紹介されました。以下に各々の詳細を記載します。 3次元再構成の応用先 近年3次元再構成を行う様々なアプリケーションが登場しており、以下のような応用が期待されています。 家具などをキャプチャして内装設計に利用 文化遺産をキャプチャして教材に利用 ドローンなどで地形をキャプチャして解析 Limitationとその対策 様々な視点からの画像を集約し各視点の位置関係と物体の3次元形状を推定するという性質上、3次元再構成が困難な物体が存在します。それらの例と、そのようなケースに対する緩和策として以下が挙げられます。 動物など動くもの。大量のカメラを使い一発で全方位から撮る。 のっぺりしていて画像から特徴的な点を抽出しにくい物体。3D laser scanningなど、カメラ以外の測定器を活用する。 鏡面反射があるため視点によって見た目が変化する物体。scanning sprayと呼ばれる、光反射を抑えるスプレーを吹き付ける。 透明な物体。scanning sprayを利用する。 未解決問題 前節とは異なり緩和が難しいケースとして以下のような困難が挙げられます。 髪の毛などの表現が非常に難しい。平面的なテクスチャでは細かい凹凸が表現できない。 複雑な形のせいでカメラに映らない箇所が生じ、出力モデルの一部が欠損する。 future challenge 3次元再構成を改良するにあたり以下のような取り組みが行われています。 物体の反射特性の推定。これによってキャプチャ時の光源環境による影響を取り除き(delighting)、合成先の光源環境に適応させること(relighting)が可能になる。 表現力の高いreflectanceモデルの適用。physically-basedな手法(5params)はLambert(3params)よりもリアルな表現を可能にする。 color correction前処理。世のカメラはphoto finishingという非線形処理を施すため、これの較正が必要。 Unstructured Object Matching using Co-Salient Region Segmentation 形状が大きく変わりうる物体、例えば: 人が着ている状態の服と畳まれている服 プールで使われている様子の浮き輪としぼんでいる浮き輪 などが写っている写真のマッチングを行いたいというのが本研究のモチベーションでした。 ドキュメントの性質によっては写真の撮り方が変わるため、商品検索やcompliance checkの際はこれを考慮した画像マッチングを行う必要があります。 (注 compliance checkについて:米国では子供用玩具に対し安全基準が定められており、amazonに出品する時はその類の書類を提出する必要があります。そしてamazon側はその書類中の画像とカタログの画像が同じ製品を撮ったものかどうかをチェックする必要があります。) Toys In Context Dataset: 子供用品のカタログ写真から収集したデータセットの提案 おもちゃ ぬいぐるみ パジャマ ... 物体検出フレームワーク SCOTTの提案 SCOTTはobjectを抽出するネットワーク(ここでsaliency mapと特徴量を得る)とマッチングを行うネットワークで構成されています。 baselineに対してF1-scoreの観点で最も良いパフォーマンスを達成したことが論文中に示されています。 Nerfels: Renderable Neural Codes for Improved Camera Pose Estimation image matching & localizationではIndirect & sparseとDense Alignmentの2つの戦略があります。それぞれのメリットとデメリットは以下です。 Indirect & sparse: 点を抽出してマッチング ORB-SLAM 3 などで使われる 処理が軽い 検出位置の精度が重要。サブピクセル単位の座標が欲しい。 Dense Alignment: 全てのピクセルを使ってポーズ推定 DTAM 4 など 処理が重い 画像の検出点周りを切り出しそれらのみをピクセルレベルでアラインメントすることで、2つのアプローチのいいとこ取りをしようというのがこの論文の目的です。 切り出された部分はカメラ移動によって見た目が変化しますが、これはNerfelという新しいレンダリング手法によって予測します。 そして検出点の位置によるreprojection lossと、Nerfelでレンダリングされた画像パッチによるphotometric lossの2つでマッチングを最適化しています。 Feature Query Networks: Neural Surface Description for Camera Pose Refinement structure-based localization あるシーンの3Dモデル(点群)が手に入っている時、クエリ画像から検出した特徴点と3次元点群との対応をとることで、2D-3D matchingによりカメラの姿勢(座標と回転の6自由度)を推定できます。しかしながら、3Dモデル構成時に使った参照画像と推論時のクエリ画像のカメラ姿勢が大きく異なる場合、同じ点に対する特徴量が異なってしまいます。本手法では、 視点に依存しない特徴量を抽出するのではなく、視点の変化に対応する特徴量が抽出できるMLPを構成し、マッチングの精度を高めました。 Feature Query Networks (FQNs) 学習するMLPへの入力はシーンの3Dモデルにおける参照点の座標(3d)・カメラの視線方向(2d)・カメラのroll回転(1d)・カメラの焦点距離(1d)・カメラから参照点までの距離(1d)の計8次元であり、ここから予測特徴量を出力します。 クエリ画像のカメラ姿勢を推定するときはFQNsに入力する情報が未知ですが、適当な推定値を初期値として以下の手順を繰り返すことで精度の良いカメラ姿勢を推定できます。 推定カメラ姿勢から特徴量を予測 クエリ画像の特徴量とマッチング RANSACでポーズ推定 推定カメラ姿勢の更新 Learning Co-segmentation by segment swapping for retrieval and discovery https://imagine.enpc.fr/~shenx/SegSwap 例えば以下のような異なるスタイルの画像間で同一パターンをco-segmentation(画像ペア中の同一物体をセグメンテーションするタスク)として検出するのが本研究の目的です。 彫刻 vs. 油画 昼画像 vs. 夜画像 スタイルの異なる画像間で同一パターンを含む公知のデータセットはないため、この研究では以下の方法で画像を生成しデータセットを構築しています。 MSCOCOデータセットのセグメンテーションタスクに登場する物体を他画像にPoisson blendで合成 Style transferで画像の見た目を多様化 Transformer, Sparse-Ncnet 5 の2つのモデルを学習して実験し、さまざまな場所の写真を集めたデータベースを基にクエリ画像の撮影場所を特定するPlace recognitionタスクを扱う以下のデータセットで評価しました。 Pitts30K 6 : Google Street Viewを用いて収集した約1万枚のピッツバーグの画像データベースから、全く異なる時期に撮られた画像をクエリとしてその撮影場所を特定する。 Tokyo 24/7 7 : Google Street Viewを用いて収集した約76000枚の昼の東京の画像データベースから、様々な時間帯で撮られた画像をクエリとしてその撮影場所を特定する。 DA-AE: Disparity-Alleviation Auto-Encoder Towards Categorization of Heritage Images for Aggrandized 3D Reconstruction クラウドソーシングで画像を収集して3次元復元しようとすると、無関係の画像が紛れ込むのでそれを除去しなければならないため、DA-AEと呼ばれるオートエンコーダーを提案しました。 これを用いて画像をクラスタリングすることで、異なる建築物の写真を分離できました。 Detecting and Suppressing Marine Snow for Underwater Visual SLAM 海底の物体を3次元再構成する時、特徴点検出器がマリンスノーを捉えてしまいその後の処理に悪影響を与えることがわかっています。 つまり海底ではRANSAC + Ratio test + Motion modelなどの後処理を加えてもSLAMがうまく動きません。 そこでマリンスノー由来の特徴点を検出結果から除去するために、Marine Snow Datasetを提案しました。 海底以外の方向を見れば基本的に単調な背景の(すなわち切り出しやすい)マリンスノー画像が得られる。 綺麗な海底画像にマリンスノーを合成する。 まともなkeypointとマリンスノー由来のkeypointを収集し、"Clean" or "Snow"を各点にアノテーションする。 これでkeypointに付随するdescriptorからそのkeypointがまともかどうかを判定する分類器が作れます。制約として、後ろがテクスチャのある背景だと分類が難しくなるようです。 A case for using rotation invariant features in state of the art feature matchers state-of-the-art (sota)なマッチング手法であるLoFTR 8 を回転に強くするために、SE(2)-LoFTRが提案されました。 これはSteerable CNNs(参考:General E(2) - Equivariant Steerable CNNs, NeurIPS 2019)の考え方をベースにしているようです。 提案されたネットワークでは、中間レイヤーや出力層での特徴量が回転不変になっています。 future workとしては以下の点が挙げられています。 transformerのpositional encodingでは回転不変性が保証されていない scale invarianceの獲得 Kaggle IMC 2022 感想戦 https://www.kaggle.com/competitions/image-matching-challenge-2022 大体同じ場所で撮られた2枚の写真をもとに、その2つのカメラの相対的な姿勢変化を求めることが本コンペティションの目標です。具体的にはfundamental matrixを求め提出することが求められます。 上位陣はsotaなlocal feature matching(LoFTR, SuperGlueなど)を活用し、複数のモデルから特徴点を集めていました。また、モデルのバリエーションだけでなく、入力画像を回転させるなどしてTest time augmentationを行なっているチームもあったようです。 1st place solution concat keypoints:すなわちキーポイント検出器のアンサンブルであり、今回のコンペでかなり重要。後段のcropがより精密にもなる。 840px、1024px、1280pxへリサイズした画像にSuperPoint+SuperGlueをそれぞれ適用し得られた特徴点を集約する 840pxへリサイズした画像にLoFTRを適用し特徴点を得る DBSCAN 9 でco-visible boxesを抽出。keypointが多く残るようなクロップを見つける どのクラスタを選択する?選択されたキーポイントの割合が閾値を超えるまでクラスタを結合 クロップは少し広めにとる クロップして再マッチング クロップからはみ出た正しいマッチングを拾うために、クロップ前の結果とクロップ後の結果をアンサンブル 5th place solution COTR 10 を改良した自作のモデルECO-TR(非公開)を用いて、SuperPoint+SuperGlueから得られた特徴点のrefinementを行った。 7th place solution LoFTRとDKM 11 のアンサンブル K-Meansでkeypointの集まっているところをクロップし再マッチング 推定Homographyを元に画像を変形し再マッチング Homography推定にはVSAC 12 を使った。Opencv MAGSAC++ 13 より速い 最後に 本ブログでは、CVPR2022の概要と参加者が興味を持ったワークショップ、Kaggle IMC 2022をご紹介しました。後編では、参加者が気になった論文を紹介するのでぜひご覧になってください。 NTT Comでは、今回ご紹介した論文調査、画像や映像、更には音声言語も含めた様々なメディアAI技術の研究開発に今後も積極的に取り組んでいきます。 アカデミックな研究に注力したくさん論文を書きたい 最新の技術をいちはやく取り入れ実用化に結び付けたい AIアルゴリズムに加え、AI/MLシステム全体の最適な設計を模索したい という方々に活躍していただけるフィールドがNTT Comには多くあります。今後も私たちの取り組みをブログ等で発信していきますので、興味を持ってくださった方は是非今後もご注目ください! Daniel DeTone, Tomasz Malisiewicz, Andrew Rabinovich. “SuperPoint: Self-Supervised Interest Point Detection and Description.” Proceedings of the IEEE/CVF conference on computer vision and pattern recognition workshops. 2018. ↩ Sarlin, Paul-Edouard and DeTone, Daniel and Malisiewicz, Tomasz and Rabinovich, Andrew. “SuperGlue: Learning Feature Matching With Graph Neural Networks.” Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2020. ↩ Mur-Artal, Raul, Jose Maria Martinez Montiel, and Juan D. Tardos. “ORB-SLAM: a versatile and accurate monocular SLAM system.” IEEE transactions on robotics 31.5 (2015): 1147-1163. ↩ Newcombe, Richard A., Steven J. Lovegrove, and Andrew J. Davison. “DTAM: Dense tracking and mapping in real-time.” 2011 international conference on computer vision. IEEE, 2011. ↩ Rocco, Ignacio, Relja Arandjelović, and Josef Sivic. “Efficient neighbourhood consensus networks via submanifold sparse convolutions.” European conference on computer vision. Springer, Cham, 2020. ↩ Torii, Akihiko and Sivic, Josef and Okutomi, Masatoshi and Pajdla, Tomas. “Visual Place Recognition with Repetitive Structures.” Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2013. ↩ Torii, Akihiko and Arandjelovic, Relja and Sivic, Josef and Okutomi, Masatoshi and Pajdla, Tomas. “24/7 Place Recognition by View Synthesis.” Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2015. ↩ Sun, Jiaming, et al. “LoFTR: Detector-free local feature matching with transformers.” Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2021. ↩ Ester, Martin, et al. “A density-based algorithm for discovering clusters in large spatial databases with noise.” kdd. Vol. 96. No. 34. 1996. ↩ Jiang, Wei and Trulls, Eduard and Hosang, Jan and Tagliasacchi, Andrea and Yi, Kwang Moo. “COTR: Correspondence Transformer for Matching Across Images.” Proceedings of the IEEE/CVF International Conference on Computer Vision. 2021. ↩ Edstedt, Johan et al. “Deep Kernelized Dense Geometric Matching”. arXiv preprint arXiv:2202.00667. (2022). ↩ Ivashechkin, Maksym, Daniel Barath, and Jiří Matas. “VSAC: Efficient and Accurate Estimator for H and F.” Proceedings of the IEEE/CVF International Conference on Computer Vision. 2021. ↩ Barath, Daniel, et al. “MAGSAC++, a fast, reliable and accurate robust estimator.” Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2020. ↩
アバター
はじめに イノベーションセンターの松下です。 NTTコミュニケーションズ株式会社 (以下、NTT Com) は、Interop Tokyo 2022の ShowNet において、 低遅延ライブ配信プラットフォーム Smart vLive による4K低遅延ライブ配信を展示しました。 この記事では、ShowNetでの展示の模様をお伝えします。 Smart vLive Smart vLiveは、1秒未満の遅延で映像をライブ配信できるサービスです。 ブラウザでの通話やWeb会議に用いられる技術であるWebRTCをライブ配信に応用することで、既存の技術では難しかった1秒未満の遅延を実現しています。 これまでWebRTCを用いたライブ配信では同時視聴者数に制約がありましたが、Smart vLiveはライブ配信に特化したシステムとして設計され、視聴者数が1万人を超える大規模なイベントにも対応できます。 また、複数のアングルを同時に配信できるマルチアングル機能により、視聴者が好みの映像を選択して楽しむこともできます。 2021年7月のサービス開始以降、スポーツ・エンタメ・オークション・金融など、様々な業界でご活用いただいています。 Smart vLiveのシステム構成 Smart vLiveは2段構成のシステムとなっています。 1段目のメディアサーバは、一般的なライブ配信サービスと同様にRTMPで映像を受信します。 メディアサーバは受信した映像をリアルタイムで処理しつつ、2段目のWebRTC配信サーバに送ります。 WebRTC配信サーバが視聴者との間でWebRTC通信を行い、映像を配信します。 多段構成を取ることで、視聴者が増加してもシステムをスケールできる構成となっています。 各コンポーネントでの映像処理では細かいチューニングを重ねて低遅延を追求しています。 ShowNetにおけるMedia over IPの取り組み 今回のShowNetでは、初めての取り組みとして Media over IP Pavilion が設置されました。 Media over IP Pavilionでは、映像と音声といったコンテンツをIPネットワークを使って伝えるための最新のソリューションが紹介されました。 IP中継車や本格的な映像関連の機器が展示され、多くの人が来場していました。 また、ShowNetのラックにもMedia over IP関連製品が展示され、これまでのShowNetでは見慣れないような製品に注目が集まりました。 ShowNetにおける展示内容 NTT Comは、Media over IP Pavilion内に設置された ShowNetスタジオ の模様を4Kでライブ配信するデモを行いました。 Smart vLiveでは1080pまでの解像度をサポートしていますが (2022年6月現在)、4K解像度で安定して低遅延ライブ配信を提供し続けられるか検証することが今回の課題でした。 今回の実験では、IP中継車から受け取ったShowNetスタジオの映像をエンコードし、ShowNetのネットワークを通じてクラウド上にあるSmart vLiveの配信エンドポイントにアップロードしています。 ShowNetスタジオでは、ShowNetに関わるエンジニアによる最新の技術に関するセッションが行われました。 セッションの内容はSmart vLiveを通じて配信され、会場の内外で視聴されました。 なかには、Interopのブース内でSmart vLiveを通じてセッションを視聴して簡易的な生中継のようにお使いいただくなど、低遅延を生かした新たな利用シーンも見つかりました。 この写真は、ShowNetスタジオのセッションをライブ配信している様子です。 写真の右側のモニタがIP中継車から受け取った映像、左側がSmart vLiveを通じて視聴している映像です。 4K解像度でも1秒未満の遅延でライブ配信できることが確認でき、来場者の方にも遅延の小ささを体感いただけました。 おわりに Interop 2022のShowNetにおける、Smart vLiveによる4K低遅延ライブ配信のデモについて紹介しました。 Media over IPの取り組みは初めてのことが多く準備期間中は様々な課題に直面しましたが、関係者の皆様のご尽力によって無事に会期を終えることができました。 今回のデモでは、Smart vLiveを用いて3日間の会期中安定して4K解像度でライブ配信を実施できました。 今後は、今回得た知見を活かしながらSmart vLiveのさらなる機能追加や品質向上に取り組んでいきます。 Smart vLiveの開発チームでは、新たなライブ配信・コミュニケーションのプラットフォーム開発を担当いただけるエンジニアを 募集しています 。 低遅延かつスケーラブルなライブ配信システムの開発は技術的にも多くの課題があり、エンジニアとして未知の領域を楽しみながら開発しています。 今回の記事を読んでご興味を持っていただけましたら、お気軽にお問合せいただければ幸いです。
アバター
はじめに こんにちは!初めまして、イノベーションセンターテクノロジー部門、Smart World 向けAI開発PJの藤原です。 我々は日々 Smart World の実現に向け、主に製造業向けのデータ分析、AIの研究開発を行なっております。 弊チームでは学会/論文投稿など積極的な学術活動も行なっており、IJCAI、KDD、CVPRなどのワークショップ採択されたり、最近ではAISTATSの本会議にも採択され ニュースリリース を出しました。 そうした学術活動の一環として、先日弊チームは技術調査 & 研究成果発表 & 企業紹介展示を目的に 2022年度人工知能学会全国大会 (JSAI2022) へ参加しました( 毎年弊チームではJSAIに数名が登壇しています! )。今年は藤原、木村、市川の3名(+インダストリアルセッションにて企業紹介1名)がそれぞれ発表を行ないました。今回はそれぞれの発表について、 因果探索編 、 異常検知編 、 データ拡張編 の三本立てでご紹介したいと思います! JSAI2022について 著名なAI関連国内学会の1つで、今回は京都で対面、リモートのハイブリッド開催でした。1つ特徴として、発表内容について募集要項が以下のようになっています。 論文の要件:論文該当分野に示される人工知能およびその関連分野の学術論文,事例報告 このように新規手法だけでなく事例報告も推奨されている点があり、企業としては特に参考になるような実施例も数多く聴講できました。NTT Com 以外にも数多くの企業が参加しており、企業ブースなども大盛り上がりでした。 今回の発表内容 では今回JSAI2022で発表した Just-In-Timeモデルを利用した非定常非線形データに対する因果探索 について、内容をざっくりと説明したいと思います! 背景 ビジネスにおけるXAIの重要性 さて前述のように我々は日々製造業向けにAIソリューションの提供や技術開発を行なっているのですが、その典型的な実施例の1つが以下のようなものになります(詳しくは、 AIプラント運転支援ソリューション を参照)。企業様から工場のセンサーデータなどを受け取り、以下のようにAIでプラント操作量の推奨値を提示するものになります。ですが、 一般的にAIはその判断根拠などがブラックボックスになりがちで、ビジネスではそこが課題となります。 一般的に、AIに対してそういった解釈性、説明性を持たせる試みを XAI (explainable AI) と言います。 因果探索とは また、特に弊チームが注力している分野に 因果 があります。因果の考え方では、相関や予測に対する寄与度を見るだけでなくデータから変数間の因果関係を求めたり、求まった因果を利用して、他の変数を固定した上である変数を動かしたときに(≒介入)どれだけの効果が生まれるか(≒因果効果)などを知ることができます。例えばプラントデータなら因果関係に基づき、生産量やCO2排出量に対してのある操作量の最適化であったり、AI判断根拠のより強力な解釈と、変数選択に基づく精度改善などを行うことができます。 今回の発表はタイトルにあるように、データのみからこのような因果関係を求める、因果探索に関する新規手法の提案になります。 実時系列データ解析の困難 注力分野のもう1つに、 時系列データ解析 があります。先述のプラントの例などで得られるデータというのは基本的に(連続)時系列データが多いです。時系列データとは、時間的な順序が意味を持つデータになります。 現実の時系列データ解析ではしばしば一般的な手法で前提とされる理想的な仮定などが崩れていて、安定した解析やAIモデルの作成が困難になります。 例えば、時々刻々システムの状態が変化している非定常性や、変数間の因果関係が簡単に記述できない非線形なものである場合があります。具体的に応用における1つ有名な困難の例を挙げると、 Concept Drift といって、学習時のデータ分布と予測時のデータ分布が異なってしまうことによる精度低下の問題などが知られています。 研究の目的 以上から、本研究の目的は以下のようになります。 背景 ビジネスなどの応用上でXAIが重要になっている。さらには踏み込んで、因果探索が重要。 課題 一方で、応用上で現れる実時系列データでは、因果探索、時系列解析における理想的な条件が崩れている。 目的 実時系列データで頻出の非定常性、非線形性、分布シフトなどに対して頑健な(≒強い/安定な)因果探索手法を開発する。 提案手法 このような非常に複雑な現実の時系列データに対して、因果関係をデータのみから読み取ることを考えます。 キーとなるアイデアは、 Just-In-Timeモデル [ Stenman 96-99 ][ 山本 13 ]と呼ばれる既存のフレームワークの応用です。 Just-In-Timeモデル Just-In-Timeモデルでは、出力を知りたい新しい入力データが来る度に、その近傍データを検索します。 検索した結果得られた近傍データセットを用いてAIモデルを学習し、入力データをそこに入力して、出力を逐次で得ます。これは、全てのデータを使い一度だけ学習したAIモデルを使い回して入力を入れて出力を得る通常のモデルとは異なるアプローチです。 近傍探索を行うことで近い状態のデータが集まることを期待でき、先ほど述べたような分布シフトの問題などに対応できると期待できます。 また、非線形の関数も入力の近傍を取ることで線形関数という単純&解釈が容易なモデルで近似できます(参考: テイラーの定理 )。 要するに、 複雑な現象も局所的に切り出せば単純な現象でモデル化できるはず 、といった感じです。 JIT-LiNGAM 因果探索には非線形の手法もいくつか知られていますが、計算量や解釈性の観点で依然問題があります。また、実時系列データの解析で起こる前述の問題(非定常性、非線形性、分布シフトなど)に対して安定性があるかは保証されません。 そこで、 上述のJust-In-Timeモデルを因果探索に応用して、比較的単純な線形の因果探索手法を使った局所的に線形、大域的に非線形な因果探索手法を考えます 。線形の因果探索手法の代表として、 LiNGAM [ Shimizu 06 ][ Shimizu 11 ]を利用します。Just-In-Timeモデルの局所線形近似を使うことで、Additive Noise Model (ANM) [ Hoyer 08 ]と呼ばれる、ノイズが加法的な広い非線形のクラスに対して、LiNGAMのアルゴリズムで解くことができるようになることを定式化により示せます(論文参照)。アルゴリズムの概要は以下の通りです。 実験 では実際にこの手法で人工データに対して実験した例を見てみましょう。今回はまず初期段階として簡単な非時系列/非線形モデルで実験します(図1)。比較対象としては因果探索アルゴリズムはLiNGAMで、観測された全てのサンプルを使うもの(Cumulative ALL)、観測されたサンプルからK個ランダムに使うもの(K-Random)が用意されています。 提案手法は距離指標や近傍選択の方法によって4パターン試しました。距離指標はユークリッド距離とマハラノビス距離のもので2種。 近傍選択方法は、入力データに対して近い順にK個のデータを抽出する方法(KNN)、入力データを中心に半径 以内にあるデータからランダムにK個抽出する方法(ERN)の2種。2種×2種で4パターンになります。 図2においてSHDという因果グラフに対する距離指標を用いて確認したところ、提案手法群の優位性を確認できます。 考察 近傍の取り方によって一定の傾向が見られたので、こちらについて考察していきます。 距離指標は観測が増えるごとにKNNだと増加傾向、ERNだと減少傾向になっています(低いほど精度が高い)。 JIT本来の期待では、観測データが増えるほど似通った状態を見つけやすくなったり、より詳細な局所モデルを作れるようになるため精度向上が期待されます。ですが、KNNでは実際にはそうなっていません。 KNNで精度が悪化している原因として、データ密度の増加による近傍半径の縮小が原因と考察します。 まず図3を見るとERNでは半径 を設定した影響で、近傍で取ってきたデータの標準偏差がほぼ一定値なのが分かります。一方で、KNNは標準偏差(≒半径)が観測増加と共に縮小している様子が確認できます。 次にこれがどのように因果探索精度に悪影響を及ぼすか考察します。図3の紫色の線が、0.05で、データ生成の際に加えたラプラスノイズのスケールを目安として表示したものになっています。KNNは半径が縮小し、最終的にはこの0.05を下回るほどにまでなっていますが、これが影響していると考えます。因果を識別するためには、変数 の内在的な揺らぎが因果関係 によって拡大され、これによって発生した揺らぎ を捉えることが必要になります。しかし、これを近傍探索によってノイズのスケールよりも小さい範囲でカットしてしまうと、観測ノイズによる揺らぎ と因果関係による揺らぎ を識別することが困難になり、因果探索アルゴリズムの精度が減少すると考えられます。今回はこのような考察を行いましたが、この点については引き続き数理的な観点を含めた検討が必要です。 まとめ 今回の発表ではJust-In-Timeモデルの近傍探索と線形の因果探索手法LiNGAMを組み合わせることで、非定常性、非線形性、分布シフトといった、現実のデータがしばしば抱える困難に対して安定性のある因果探索手法、JIT-LiNGAMを提案しました。また提案手法について、基礎的な実験を行い、従来のLiNGAMに対しての優位性を示すと共に、有効な近傍探索方法への示唆を得ました。 今後の発展としては、時系列モデルへの拡張、他の非線形因果探索モデルとの比較実験、近傍探索方法が精度に与える影響についてより数理的な考察などを行っていく予定です。 おわりに 以上が発表内容になります!今回は数式を抑え目に、あくまで何が課題で、何ができるのかと、その実現アルゴリズムの最低限の部分を重視してお伝えしました。JIT-LiNGAMの詳細や定式化については、 今回の論文 や、 参考文献 をご覧になって下さい。 宣伝 Node-AI/データ分析コンサルティング 私の所属するイノベーションセンターテクノロジー部門 Smart World向けAI開発PJ では、日々AI/データ分析に関するアルゴリズムなどの研究開発をしています。これらの技術は同じくイノベーションセンターテクノロジー部門の Node-AI PJ によって内製開発されているノンコーディングAIモデル開発ツール、 Node-AI (本ブログでの解説記事は こちら ) に搭載されたり、同じく データ分析コンサルティングPJ と共同で、データ分析案件にてお客様に提供されています。 特に、Node-AIにはまだ今回の発表のアルゴリズムは乗っていませんが、基本的な時系列因果分析機能が搭載されており、今回の例のようにデータ間の因果関係を求めグラフ上に可視化できます。またこれは時系列因果モデルなので、時間遅れを考慮した変数間の因果関係を知ることができ、「センサーAの値が5分遅れでセンサーBに効いている」といった解析が可能です! 弊チームでは、企業様と共同での今回のような因果分析を含め主に時系列データ解析/予測/異常検知/最適化を対象としたPoC、各種究機関との共同研究案件、Node-AIのご契約を募集中ですので、ご興味があればこちらまでご連絡ください!(メール:ai-deep-ic[at]ntt.com) 採用 弊チームではリサーチャー、MLエンジニアを募集中です。今回のようにメンバーの学会発表も推奨され、前述のようなチーム連携により開発アルゴリズムが即座にビジネスサイドで検証、社会実装される素晴らしい環境です。ぜひご応募下さい。採用情報については こちら からよろしくお願いします。(新卒入社をお考えの場合は新卒採用情報をご覧下さい) 参考文献 [今回の論文] 藤原 大悟, 小山 和輝, 切通 恵介, 大川内 智海, 泉谷 知範, 浅原 啓輔, 清水 昌平: Just-In-Timeモデルを利用した非定常非線形時系列データに対する因果探, 人工知能学会全国大会論文集, Vol. JSAI2022, pp. 3E4GS205-3E4GS205, (2022), 論文リンク(フリー) [Stenman 96-99] Stenman, A., Gustafsson, F., Ljung, L.: Just in time models for dynamical systems, Proceedings of 35th IEEE Conference on Decision and Control, Vol. 1, pp. 1115--1120 (1996) など [山本 13] 山本茂:Just-In-Time 予測制御: 蓄積データに基 づく予測制御, 計測と制御, Vol. 52, No. 10, pp. 878–884 (2013) [Shimizu 06] Shimizu, S., Hoyer, P. O., Hyv¨arinen, A., Kerminen, A., and Jordan, M.: A linear non-Gaussian acyclic model for causal discovery., Journal of Machine Learning Research, Vol. 7, No. 10 (2006) [Shimizu 11] Shimizu, S., Inazumi, T., Sogawa, Y., Hyv¨arinen, A., Kawahara, Y., Washio, T., Hoyer, P. O., and Bollen, K.: DirectLiNGAM: A direct method for learning a linear non-Gaussian structural equation model, The Journal of Machine Learning Research,、 Vol. 12, pp. 1225–1248 (2011) [Hoyer 08] Hoyer, P., Janzing, D., Mooij, J. M., Peters, J., and Sch¨olkopf, B.: Nonlinear causal discovery with additive noise models, Advances in neural information processing systems, Vol. 21, pp. 689–696 (2008)
アバター