TECH PLAY

NTTドコモビジネス

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

602

この記事は、 NTTコミュニケーションズ Advent Calendar 2023 11日目の記事です。 はじめに こんにちは。コミュニケーション&アプリケーションサービス部の石井です。 今年はAI分野においては LLM 1 の話題で持ちきりの一年でしたが、そんな LLM とは全く関係のないグラフニューラルネットワーク(以下、GNN)の説明性に関する手法である GNNExplainer を題材に扱っていこうと思います。 GNN 2 とはグラフで表現された構造化データを深層学習で扱うためのニューラルネットワーク手法の総称です。グラフデータはさまざまな事象を表現できる可能性を秘めていて、GNN の予測結果を解釈できれば、人との関係性把握やマーケティングへの応用など幅広い活用が期待できると思っています。GNN に興味がない方もこんな技術があるのかと深く考えずに読んでもらえればと思います。 本記事で扱う内容 本記事では以下の内容について扱います。 説明可能な AI(XAI)について GNNExplainer とは GNNExplainer の実践 説明可能な AI(XAI)や GNNExplainer に関しての簡単な概要説明をした後に、サンプルデータを用いて構築したモデルに GNNExplainer を当てはめてみた結果についてコードと併せて解説をしていきます。説明可能なAI(XAI)や GNNExplainer の概要についてはすでに知っているという方は記事後半の「GNNExplainer の実践」まで飛ばして見てください。 説明可能なAI(XAI)について 機械学習モデルにおける意思決定を行う際に、そのモデルが下した判断を人間が理解できるように説明することを目的とした技術の総称を「説明可能なAI(XAI) 3 」と言います。昨今の技術発展は、ディープラーニングを皮切りにより高い予測精度を達成している一方で、予測における説明可能性というのは複雑化する関数近似によって犠牲になっています。つまり、予測精度と説明可能性の間にはトレードオフの関係があり、昨今では機械学習モデルを人間が理解することは極めて難しくなっています。 では、なぜ説明可能なAI(XAI)が大事なのかというと、それは現実世界やビジネス領域では意思決定における透明性や不偏性が要求されるためです。例えば、医療分野である人の病気の発症リスクを予測する判定を機械学習モデルで行い、予測結果として発症リスクが高いとの判断をした場合には、その結果と根拠理由を示さなければ納得が得られず信用を損なう可能性があります。 このように説明可能性は現実世界では極めて重要な要素の1つとなっており、一般的に人間は自分が解釈したり理解できないものを採用しない傾向があるため、機械学習モデルの説明可能性はあらゆるシーンにおいて無視できないものとなっています。そのため、 GNN においても同様にモデルの説明可能性は要求されることから、 GNN における説明性に焦点を当てた技術が開発されてきています。 GNNExplainer とは GNNExplainer 4 とは、2019年に NeurIPS 5 で採択された論文である「 GNNExplainer: Generating Explanations for Graph Neural Networks 」の中で提案された GNN の説明性に関する手法です。 GNNExplainer は学習済みの GNN モデルと予測結果を入力として与えると、出力として予測に影響を与えたノードの特徴量と予測を説明するサブグラフを返すことで予測結果を説明することを可能にします。 また、model-agnostic な手法であるため、特定のモデルに依存することなく扱うことができます。加えて、GNN で扱う問題設定にはいくつかの種類がありますが、ノード分類、リンク予測、グラフ分類など一般的なグラフの問題設定に対応しているため適用範囲が広いことが言えます。 論文内では、実世界のデータセットを用いて、GNNExplainer の説明性における妥当性を定量分析と定性分析の両側面から評価してどれくらい有効であるかを述べています。興味がある方は、論文を参照して詳しい実験内容について見てください。 GNNExplainer のアルゴリズムは、ノード が与えられた際に予測根拠をよく説明するようなサブグラフ とノードの特徴量 を特定することを目指していきます。そして、ここで述べているよく説明ができているサブグラフ を、当該アルゴリズムでは相互情報量 を最大化するような と定義しています。 上記の定式は、エントロピー と条件付きエントロピー の差分を最大化することを意味しており、学習済みの GNN では予測確率は固定であることからエントロピー の項は一定となるため、条件付きエントロピー の項を最小化するようなサブグラフ を探索していくことを実質的に行います。 もう少し簡単に説明すると、全体のグラフから対象ノード とは別のノードである を除外した際の予測確率 の増減を見て、予測確率が大きく減少する場合はノード は予測に良い影響を与えると判断して、予測に大きく寄与するエッジのみを選択していくことで有効なサブグラフの獲得を目指していきます。 実際には、このサブグラフの選定の際には全探索すると計算コストが膨大となるため、直接最適化問題を解くことはせずに条件付きエントロピーの式をイェンセンの不等式や平均場近似を用いてエッジの存在有無を示す期待値に変換して、サブグラフの隣接行列を計算して求めていくことで実現します。この辺りの詳細な説明については 元の論文 を参照ください。 GNNExplainer の使い方 GNNExplainer は PyTorch Geometric 6 内のモジュールとしてすでに実装済です。そのため、当該フレームワークを利用することで簡単に GNNExplainer の処理を再現できます。 PyTorch Geometric ではバージョン2.2から説明性のフレームワークとして explain モジュールを提供しており、さまざまなアルゴリズムを用いて GNN の説明性生成や可視化のための柔軟なインターフェースを提供しています。 PyTorch Geometric では GNNExplainer 以外のアルゴリズムとして、CaptumExplainer や PGExplainer 7 、 AttentionExplainer 8 などのアルゴリズムが用意されており利用することが可能です。以下にそれぞれの特徴をまとめます。 GNNExplainer はノードの重要特徴量だけでなくグラフトポロジに基づいた重要なサブグラフを同時に明らかにする GNN に対して有効な説明手法を提案した最初の技術です。単一のインスタンス(予測対象であるグラフやノードの単位)に対して、独立した説明性に関わる特徴量やサブグラフを生成するため、その説明性を対象としていない別のインスタンスに一般化することが困難であるという課題があります。しかし、複雑な GNN の理解に際して解釈を一助することは間違いないため、適切な状況下での利用や他の手法と組み合わせて解釈を補うことが可能です。 CaptumExplainer は Integrated Gradients 9 と呼ばれる勾配積分法の公理に基づいて各次元における特徴量の寄与度を算出する手法で、算出過程がモデル実装に依存しないため、model-agnostic な手法に分類されています。また、Integrated Gradients はあらゆる微分可能モデルへの適用が可能なため、GNN に限らずさまざまなモデルで説明性の技術として適用されています。一方で、Integrated Gradients はグラフに特化した手法ではないことから、ノード間などの相互作用などが考慮されないため、説明性についても当然に相互作用が考慮されない形で出力されるといった課題があります。 PGExplainer は GNNExplainer で課題となる単一インスタンスに制限される課題を、説明性の生成過程をニューラルネットワークよりパラメータ化することで、一連のインスタンスの予測をまとめてモデル全体としての説明性を取得することを可能にした手法です。こちらも model-agnostic な手法となっています。また、一連のインスタンスを予測する際に GNNExplainer では新しいインスタンスに対して再学習を要するが、PGExplainer では一度学習した説明器のモデルを帰納的な設定のみで新しいインスタンスを説明可能となることから、再学習を必要とせず大規模なデータセットに対しても手法適用が可能とされています。 ちなみに余談にはなりますが、PyTorch Geometric と同様に GNN を扱うフレームワークである DGL 10 でもバージョン 1.0.0 以降で GNNExplainer 等をサポートしています。どちらのフレームワークでも GNNExplainer を扱うことができるため、ご自身ですでに使い慣れているフレームワークに合わせて選択されると良いかと思います。 GNNExplainer の実践 ここからは実際にサンプルデータを用いて、構築した GNN モデルに GNNExplainer を適用して予測を解釈することを試していこうと思います。 実行環境・インストール まずは実行環境です。 今回は以下の内容で PyTorch Geometric が扱える環境を用意しました。 # cat /etc/os-release NAME="CentOS Linux" VERSION="7 (Core)" ID="centos" ID_LIKE="rhel fedora" # pip list | egrep "(torch)" torch 2.1.1 torch-cluster 1.6.3+pt21cpu torch_geometric 2.4.0 torch-scatter 2.1.2+pt21cpu torch-sparse 0.6.18+pt21cpu torch-spline-conv 1.2.2+pt21cpu torchaudio 2.1.1 torchvision 0.16.1 PyTorch や PyTorch Geometric のインストールは公式サイトに分かりやすく記載していますのでそちらを参照してコマンドを実行してみてください。 データ準備 今回はベンチマークとしても利用されているオープンデータである Amazon Dataset 11 を利用します。 このデータセットはノードが商品を示し、エッジは共同購入されたことを表したグラフデータです。 ノード特徴量は商品レビューを bag-of-words 12 によりベクトル変換したデータとなっており、745次元の特徴量を持っています。また、ノードのターゲットラベルは商品カテゴリを示しており、8つの商品カテゴリのいずれかに該当します。 項目 内容 ノード数 7,650 エッジ数 238,162 ノード特徴量(次元数) 745 クラス数 8 文章では少し分かりづらい部分もあるかと思いますので、ノードとエッジの関係性を簡易に示した図と実際のデータを NetworkX 13 にて可視化したグラフを載せておきます。 問題設定 問題設定はシンプルにマルチクラスのノード分類問題を考えます。以下に概要を示します。 予測対象となる商品ノードが8つの商品カテゴリのどれに当てはまるのかを予測する GNN モデルを構築することを目指します。 加えて、今回は説明性を獲得したいので構築した GNN モデルに対して、GNNExplainer を適用してその得られた説明性について確認していこうと思います。 モデル解釈 本題のモデル構築と GNNExplainer による説明性の獲得についてコードを載せながら述べていきます。まずは、GNN モデルを作成します。 import random from math import sqrt from collections import Counter import networkx as nx import torch import torch.nn as nn import torch.nn.functional as F import torch_geometric.transforms as T from torch_geometric.explain import Explainer, GNNExplainer from torch_geometric.explain.metric import fidelity from torch_geometric.explain.metric import groundtruth_metrics from torch_geometric.nn import GCNConv from torch_geometric.utils import to_networkx from torch_geometric.datasets import Amazon # パラメータ指定 DIM = 16 # データ読み込み dataset = Amazon(root= '../data' , name= 'Photo' ) data = dataset[ 0 ] # データ分割(学習データ:テストデータ:バリデーションデータ=7:2:1) split = T.RandomNodeSplit(num_val= 0.1 , num_test= 0.2 ) data = split(data) # モデル定義 class Model (nn.Module): def __init__ (self, num_features, dim= 16 , num_classes= 8 ): super ().__init__() self.conv1 = GCNConv(dataset.num_node_features, dim) self.conv2 = GCNConv(dim, num_classes) def forward (self, x, edge_index): x = self.conv1(x, edge_index) x = F.relu(x) out = self.conv2(x, edge_index) return F.log_softmax(out, dim= 1 ) def train (model, data, optimizer, criterion, epochs= 100 ): for epoch in range ( 1 , epochs + 1 ): model.train() optimizer.zero_grad() out = model(data.x, data.edge_index) loss = criterion(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() pred = out.argmax(dim= 1 ) acc_train = int ((pred[data.train_mask] == data.y[data.train_mask]).sum()) / int (data.train_mask.sum()) acc_val = eval_acc(model, data, data.val_mask) if epoch % 10 == 0 : print (f 'Epoch: {epoch:03d}, Train Loss: {loss:.3f}, Train Loss: {acc_train:.3f} ,Val Acc: {acc_val:.3f}' ) return model def eval_acc (model, data, mask): model.eval() pred = model(data.x, data.edge_index).argmax(dim= 1 ) correct = (pred[mask] == data.y[mask]).sum() acc = int (correct) / int (mask.sum()) return acc # パラメータセット device = torch.device( 'cuda' if torch.cuda.is_available() else 'cpu' ) data = data.to(device) model = Model(dataset.num_node_features, dim=DIM, num_classes=dataset.num_classes).to(device) optimizer = torch.optim.Adam(model.parameters(), lr= 0.005 , weight_decay= 5e-3 ) criterion = nn.CrossEntropyLoss() # モデル学習とテストデータによる精度評価 model_gnn = train(model, data, optimizer, criterion, 300 ) acc_test = eval_acc(model_gnn, data, data.test_mask) print (f 'Test Acc: {acc_test:.3f}' ) 上記のコードでは Amazon Dataset のデータ読み込みからモデル学習、評価までを実装しています。 学習結果はテストデータでの accuracy が 93.7% で正しく学習できている様子が伺えます。なかなかの高精度ですね。各カテゴリ別の正答一致数を混同行列より確認してみても、ほとんどのカテゴリで正しく予測が出来ていることが見てわかります。 さて、ここまでで構築された GNN モデルを用いて、いよいよ本題の GNNExplainer の実装に移っていきます。 先ほど学習したモデル( model_gnn )を Explainer クラスのパラメータとして渡して説明性を取得していきます。 # サブグラフを可視化する関数 def viz_subgraph (edge_index, edge_weight, target, node_index): target_color = '#FFFFFF' color_list = [ '#FCFFA4' , '#F7E425' , '#FEBA2C' , '#F89540' , '#F2844B' , '#E16462' , '#CC4778' , '#B12A90' ] if edge_weight is not None : edge_weight = edge_weight - edge_weight.min() edge_weight = edge_weight / edge_weight.max() if edge_weight is not None : mask = edge_weight > 1e-7 edge_index = edge_index[:, mask] edge_weight = edge_weight[mask] if edge_weight is None : edge_weight = torch.ones(edge_index.size( 1 )) subgraph_idx = np.unique(explanation.edge_index[:, mask][ 0 ]) target_idx = np.where(subgraph_idx == node_index)[ 0 ] target = [color_list[idx] for idx in target[subgraph_idx]] target[target_idx[ 0 ]] = target_color g = nx.DiGraph() node_size = 800 for node in edge_index.view(- 1 ).unique().tolist(): g.add_node(node) for (src, dst), w in zip (edge_index.t().tolist(), edge_weight.tolist()): g.add_edge(src, dst, alpha=w) plt.figure(figsize=( 15 , 8 )) ax = plt.gca() pos = nx.spring_layout(g) for src, dst, data in g.edges(data= True ): ax.annotate( '' , xy=pos[src], xytext=pos[dst], arrowprops= dict ( arrowstyle= "->" , alpha=data[ 'alpha' ], shrinkA=sqrt(node_size) / 2.0 , shrinkB=sqrt(node_size) / 2.0 , connectionstyle= "arc3,rad=0.1" , ), ) nodes = nx.draw_networkx_nodes(g, pos, node_size=node_size, node_color=target, margins= 0.1 ,) nodes.set_edgecolor( 'black' ) nx.draw_networkx_labels(g, pos, font_size= 10 ) plt.show() plt.close() # explainerインスタンスの定義 explainer = Explainer( model=model_gnn, algorithm=GNNExplainer(epochs= 200 ), explanation_type= 'model' , node_mask_type= 'attributes' , edge_mask_type= 'object' , model_config= dict ( mode= 'multiclass_classification' , task_level= 'node' , return_type= 'log_probs' , ), ) # 説明性に関わるノード指定と演算処理 node_index = 700 explanation = explainer(data.x, data.edge_index, index=node_index) # ノード特徴量における重要変数の可視化 explanation.visualize_feature_importance( "feature_importance.png" ,feat_labels= None , top_k= 10 ) # 指定したノードに関係するサブグラフ可視化 viz_subgraph(explanation.edge_index, explanation.edge_mask, explanation.target, node_index) 初めに定義している viz_subgraph 関数は GNNExplainer より得られた結果のエッジ情報及びマスク情報を元にサブグラフを可視化する処理を定義しています。 次いで、Explainer クラスを用いてインスタンスを作成することで、ノードの重要変数とサブグラフ取得などの説明性の獲得を行なっています。この Explainer クラスは全ての説明性に関わるパラメータを扱うようにデザインされたクラスで、フレームワーク利用者はこのクラスのパラメータを変更することで共通処理のままで複数のアルゴリズムを操作すること可能にしています。今回は GNNExplainer を利用するため、 algorithm の GNNExplainer を指定して、 model_config に GNN モデルのタスクに合わせて適切なパラメータを設定しています。 Explainer クラスのインスタンスを作成した後は、入力データと説明性の対象とするインスタンス(今回はノード)のインデックス番号を与えることで予測における説明性情報を取得します。インスタンスのインデックス番号を適当に 700 とした場合の結果は以下のようになりました。 まずは、ノードにおける重要変数(Feature Importance)ですが、重要度が高い方から top_k で指定した数だけ特徴量を棒グラフで可視化しています。今回のデータセットではノード特徴量は bag-of-words によってベクトル化した情報のため、どのようなワードが重要であるかを当該データから判別はできませんが、意味のあるラベル付きの情報であった場合は何が予測に効いているのかを把握するのに有効な方法だと思います。 最後に、ノードインデックスが 700 の予測に寄与しているサブグラフを可視化して見てみると、 700 の商品ノード(白い丸)には 2008 と 6347 の商品ノードが予測に密接に関係しているという結果が見られます。また、予測に寄与しているサブグラフの商品ノードは、ほとんどが同一カテゴリ(赤丸のノード)であることから、同一カテゴリの商品と一緒に購買されていていることが分かります。特に 2008 は単独で同時に購買されているが、 6347 はその他の関連する商品と同時に購買されていることが言えそうです。 GNNExplainer では特定のノードに焦点を当てているため、グラフデータ全体としての説明性を明言することは難しいですが、このように単一のノードを起点にした説明性の理解から大まかな全体傾向を掴むなどの活用に期待はできそうですね。 終わりに 今回は GNNExplainer の概要とモデルに対しての適用方法について紹介しました。 GNN によってグラフ構造をニューラルネットワークで扱えるようになりましたが、グラフ構造自体の複雑性も相まって説明性・解釈性は非常に高いわけではありません。 そのため、今回紹介した GNNExplainer などによる GNN の予測根拠の説明性向上は、追加特徴量の検討や予測根拠によるマーケティング活用など幅広く応用が効くものになるかと思います。 まだまだ、発展途上の分野ではありますが、今後もXAI領域の発展には期待したいですね。 それでは、明日の記事もお楽しみに! Large Language Model の略で、極めて大量のデータと深層学習技術によって構築された言語モデルです。 ↩ こちら で GNN についての解説が詳しく記載されています。 ↩ https://www.darpa.mil/program/explainable-artificial-intelligence ↩ https://arxiv.org/pdf/1903.03894.pdf ↩ https://nips.cc/ ↩ https://pytorch-geometric.readthedocs.io/en/latest/ ↩ https://arxiv.org/pdf/2011.04573.pdf ↩ Attention ベースの GNN により Attention 係数をエッジの説明に応用した解釈性の手法です。Attention 機構を扱った特定のアルゴリズムで学習したGNNモデルのみが利用可能となります。 ↩ https://arxiv.org/pdf/1703.01365.pdf ↩ https://www.dgl.ai/ ↩ https://arxiv.org/pdf/1811.05868.pdf ↩ 文章中に出現する単語の順番は考慮せずに、単語の出現回数のみからベクトルを表現する手法を指します。 ↩ https://networkx.org/ ↩
アバター
この記事は、 NTT Communications Advent Calendar 2023  8 日目の記事です。 はじめに こんにちは、イノベーションセンターでノーコード分析ツール「Node-AI」開発チームの林です。 業務としては Node-AI のフロントエンドやバックエンド開発、最近では監視/可視化のプラットフォーム開発に携わっています。 本記事ではこの監視/可視化のプラットフォームについて、検討段階ではあるのですがアーキテクチャを中心にまとめていきたいと思います。 Node-AI について Node-AI はノーコード分析ツールとなっていて「予測/異常検知モデルをすぐに・簡単に・わかりやすく作成可能」といったところを推しているツールとなっています。 インフラとしては、Google Cloud を利用しており Google Kubernetes Engine (以下、GKE)の上でアプリケーションが実行されています。 運用上の課題 現在は誰しもが同様に利用できる環境を提供していたり、個別の顧客環境としてカスタマイズしたものを提供したりと Google Cloud のプロジェクトレベルで別れた環境を複数提供しています。 そうなってくると運用する上で面倒なのがメトリクスやログといったテレメトリー情報が各プロジェクトに集約されていることです。コンソール上でプロジェクトを切り替えれば Cloud Monitoring や Cloud Logging の Explore で確認できますが、可能であれば 一元的に集約して「ここを見るだけ Node-AI の現状がわかる!!」 という状態にしたいと思いました。 また、 各環境を横串で分析したい という時もプロジェクトごとに BigQuery に蓄積していると一工夫が必要だったりするので、こういったケースでも一元的に集約できていると嬉しいことがありそうです。 加えて、 目的に応じてプロジェクトを切り出すことで権限制御の簡素化や IaC の肥大化を防ぐといったメリット もあると考えるため、その点も考慮したいと思います。 「テレメトリー集約基盤」爆誕(の予定) 「各環境のテレメトリーを集約」 をテーマに新しくプロジェクトを切り出して下記のようなアーキテクチャを検討しています。よくあるログの集約/可視化パターンではあるのですが、いくつか推しポイントがあるのでその点をご紹介したいと思います。また、これらの推しポイントの裏テーマとして 「極力手間をかけずに」 というのもかかげていたりします。 推しポイント① Grafana + Cloud Run 可視化のツールとして Grafana を採用して Cloud Run 上にホスティングしています。Cloud Run Service でホスティングすることで「運用負荷の軽減」「コスト削減」の 2 点のメリットが得られると考えたためです。 「運用負荷の軽減」 という点では、みなさんご存知の通り Cloud Run はサーバーレスサービスというところで Compute Engine など IaaS を利用した際の面倒ごと(例えば、柔軟性が高いが故の初期セットアップでのネットワークやセキュリティに関する適切な設定作業など)が少ない のが非常に嬉しいところです。 「コスト削減」 という点では、Cloud Run Service の最小インスタンス数を 0 にしておくことでリクエストがない間は 自動スケーリング機能 によりインスタンス数が 0 になります。インスタンス数が 0 のメリットとしてはその間課金されないところです、一方でリクエストを契機に起動することになるのでインスタンス数が 0 の状態での初回リクエストでは時間がかかる(コールドスタート)点がデメリットでもあります。 今回のユースケースでは Grafana のダッシュボードを見られれば良いのでコールドスタートを許容できます、なので最小インスタンス数を 0 にした自動スケーリング機能を活用してコスト削減の恩恵を受けられる形になっています。 推しポイント② Cloud Run + Wildcard Certificate + Identity-Aware Proxy Grafana on Cloud Run を運用者がアクセスするにあたって Cloud Load Balancing を利用してインターネット公開しています。公開にあたり「セキュリティ強化」と「今後の運用を見据えたドメイン紐付け」について紹介します。 Cloud Run Service でホスティングしてインターネット公開する方法はいくつかあると思いますが、「 セキュリティ」 を意識した時に Cloud Load Balancing や Cloud Armor を前段に置くパターンが一般的かと思います。今回はそれらに加えて、Identity-Aware Proxy(以下、IAP) というプロキシサービスを活用しています。こちらを導入すると Grafana にアクセスすると下記のように Google アカウントの認証画面に移動して、適切な権限を持っている Google アカウントでないと認証を通さない設定を簡単に入れる ことができます。 「今後の運用を見据えたドメイン紐付け」 という点では、Cloud Run Service の便利な機能である URL マスク と Certificate Manager による Wildcard Certificate を組み合わせて 1 つの DNS 設定で複数のサブドメインとサービスを紐づけられることができまる構成をとっています。こちらの導入により、今後運用に必要なツールを採用したい時に Cloud Run Service に新たなツールをデプロイするだけで追加の IP の払い出しや Load Balancer のプロビジョニングなどが必要なくなります。 推しポイント③ Grafana Plugin + Cloud Monitoring + Cloud Logging Grafana で Kubernetes のメトリクスやログを可視化しようとすると Prometheus, Promtail, Grafana Loki といった複数のコンポーネントが思い浮かびます。これらのコンポーネントを極力増やさない面倒をみないように工夫した「マネージドサービスと Plugin による拡張」について紹介します。 Prometheus については、 Google Cloud Managed Service for Prometheus という機能があり GKE 1.27 以降の新規クラスタではデフォルトで有効化されています。名前の通り、マネージドな Prometheus となっていて 利用者が新たにデプロイする必要がない点が手間がかからず嬉しいところ です。 また、テレメトリーの蓄積する場所も必要になってくるかと思います。ストレージ周りの追加の管理は大変な面もあるので、この点もマネージドに寄せられたらと思いました。Grafana を漁っていると今年の 3 月に Cloud Logging に接続できるプラグインがリリースされていることがわかりました。Grafana にはデフォルトで Cloud Monitoring のプラグインも導入されていて、これらを採用することで Grafana から Cloud Operations のサービスを利用でき、新たなコンポーネントの追加や管理から解き放たれました! まとめ 検討中のテレメトリー集約基盤について紹介しました!工夫のポイントとして 3 つ挙げています。 Grafana + Cloud Run による「運用負荷の軽減」「コスト削減」 Cloud Run + Wildcard Certificate + Identity-Aware Proxy による「セキュリティ強化」「今後の運用を見据えたドメイン紐付け」 Grafana Plugin + Cloud Monitoring + Cloud Logging による「マネージドサービスと Plugin による拡張」 この中でも 「極力手間をかけない」 という点も意識してマネージドサービスを採用したり置き換えたりしてみました。とはいえ、構築して終わりではなくこの基盤を使って「どのように運用を高度化するか」というのが重要だと思っています。 メトリクスやログを可視化して、 実際にサービスをユーザーに使ってもらう中で何を SLI として SLO を設定した上で、運用から得られるフィードバックを開発に活かすというサイクルを回せるところまで取り組めたらと思っています! DevOps の実践、楽しみです。 今後の展望 デフォルトで収集できるメトリクスやログだけでなく、 OpenTelemetry の導入によるさらなるテレメトリー収集も並行して検討 しています。こちらはチームの優秀なメンバーが担当してくれていて、近日中に導入できそうとか。非常に楽しみです。 将来的にはトレースとログを紐づけて分散トレースにも対応できる状態を目指しています。こちらも形になったら記事にしたいと思います! 最後まで、ご覧いただきありがとうございました! 参考記事 GKE で手間をかけずに Let's Observability!-前編- GKE で手間をかけずに Let's Observability!-後半- Cloud Certificate Manger による Wildcard Certificate を用いた Cloud Run の活用 Cloud Run を徹底解説!
アバター
この記事は、 NTT Communications Advent Calendar 2023 及び 高専キャリア Advent Calendar 2023 の7日目の記事です。 皆さんこんにちは、 SDPF クラウド/サーバー 仮想サーバーチームの宮岸( @daiking1756 )です。 昨日の 6日目の記事 を書いた @Kumassy_ と同じく、 普段はOpenStackベースの仮想サーバー基盤やバックエンドストレージ基盤の開発・運用をしています。 この記事では、私が2023/11/18に開催された HNK全国高専交流会 2023 in 白山 のLT枠で発表した 高専かるた について紹介します。 また、初めて オープンデータ に貢献して思ったことを書こうと思います。 LT枠での発表の録画と発表資料は公開されておりますので、興味がある方はぜひご覧下さい。 www.youtube.com docs.google.com 私と高専について 高専かるたについて 高専オープンデータについて なんで作ったの? 校章に興味を持ったきっかけ💡 何もしないうちに7年が経過⏳ 由来がないので追加し、ついでにかるた化🎴 皆さまからのデータ提供をお待ちしています🙌 おわりに 私と高専について タイトルにもある通り、この記事では高専が1つのテーマとなります。 そこでまずは簡単に私と高専の関わりをまとめた画像を載せておきます。 初対面の人と話すときに何か共通点があると話が弾むと思いますが、私の経験上「高専出身」というのは抜群に話が弾みます。 マイノリティであるが故のあるあるネタ、高専独特の間合い、全国高専〇〇大会話、止まらない寮の珍事件など、話題に事欠きません。 ※ あくまで私個人の感想です。 会社に入ってからも、高専出身という部分から広がった人脈が多くあり、嬉しい限りです。 高専かるたについて さて、今回作った 高専かるた を紹介します。 下記のURLからアクセスできます。 https://codeforkosen.github.io/kosen-apps/karuta.html また関連リンクは下記のサイトにまとめているので、こちらも載せておきます。 protopedia.net 高専かるたにアクセスすると画像の通り、63高専の校章と読み札が1枚表示されております。(画像の読み札は「石川高専」) 分からない場合はヒントボタンを押すと、読み札の校章の由来を確認できます。 正しい札を取ると、取った札は消えていきます。 最後の1枚まで到達したときの御手付きの回数によって、最後に表示される称号が変わるようになっています。 高専博士の称号を目指して遊んでみて下さい。 FYI: 現在はイージーモード中です https://t.co/OBp11ho3bn の #高専かるた ですが、 最後の1枚まで進むと御手付きの回数に応じた称号が得られます。 目指せ高専博士! (現在は「取札を強調」が使い放題なイージーモード中...) #高専 pic.twitter.com/F4uSZqRTL0 — daiking⊿🌗 (@daiking1756) 2023年11月19日 x.com 元々 高専オープンデータ を使ったサンプルページとして 高専の校章一覧ページ が公開されていました。(詳細は後述) このページにかるた機能を追加したものが、今回作った 高専かるた です。 高専オープンデータについて 今回お世話になった高専オープンデータは下記のリポジトリで管理されています。 github.com 今回は高専の校章に関するオープンデータ( data/kosen_school_emblem.csv )を利用しましたが、他にも下記のようなオープンデータがありました。 高専環境報告書オープンデータ(公式) 高専キャンパスオープンデータ(非公式) 高専プロコンオープンデータ(非公式) 高専カリキュラムオープンデータ(非公式) 高専カレンダーオープンデータ(非公式) 下記の通り、オープンデータの提供は大募集中のようで、高専有識者の方はぜひプルリク出しましょう。 高専の方、オープンデータ提供、お願いします! 各高専、各学科の非公式オープンデータの追記(プルリク)いただける方も、大募集! なんで作ったの? さて、流れが唐突だったので、これを読んでいる多くの方は「なんで高専かるた作ったの?」状態になっていると思います。 説明しても納得されない気もするのですが、ここからは高専かるたを作った背景を順番に書いていきます。 校章に興味を持ったきっかけ💡 まずは校章に興味を持ったきっかけについて書きます。 時は約7年前、私が高専在学中のある日の全校集会のこと。 早く集会終わらないかななどと考えながらぼーっと前方を眺めていたところ、ふとステージ中央の上部に置かれている母校石川高専の校章に目が留まりました。 当時の私:「ん・・・?この校章は・・・?石と川で高専という文字が挟まれてできている!!なんと!!」 驚愕の事実に気付いた私は 由来 を調べてみました。 すると、確かに睨んだ通り下記の由来が書かれていました。 「石川高専」であることを明確に打出したもの, というアピール性に眼目をおいて「高専」の文字を「石」と「川」で両側から円形に囲み, 創造と協調の精神が生きたわかりやすいものにしました。 当時の私:「校章って奥深い・・・。他高専はどうなってる?高専の校章一覧サイトとかあると面白いのでは?」 こうして私は校章に興味を 持ちそうに なりました。 何もしないうちに7年が経過⏳ 校章に興味を持ちそうになったものの、結局何もせずに時間が経ち、次第に当時の熱も冷めきっていきました。 そうして約7年の時が経った2023年10月。 とある記事を見つけます。 fukuno.jig.jp なんと、私がぼーっとしている間に 高専の校章一覧ページ ができていました!(ありがたい) 早速アクセスしてみると、各高専の校章にはそれぞれ特徴があり、想像通り眺めていて面白いものでした。 ただし、そこには私が欲していた校章の由来データは見つかりませんでした。 うーん、これは困った。 由来がないので追加し、ついでにかるた化🎴 無ければ自分で追加しよう!ということで、各高専のホームページを巡る旅に出ました。 地道に巡った結果、校章データが登録されていた63高専のうち、41高専のデータはホームページから取得できましたが、 残る22高専のデータは埋めることができませんでした。 github.com これが私にとって初めてのオープンデータへの貢献でした。 提供する側はデータの正確性や出典など気を遣う部分も多いですが、自分自身が欲しているデータがオープンデータとして追加されるのは気持ちが良いものですね。 そしてこのデータがまた別の活用をされていくと思うと、何だかワクワクします。 こうして校章とその由来情報が揃ったことで、「なんか上の句と下の句みたいでかるた作れそうだな」という発想が生まれ、高専かるたが作られたのでした。 皆さまからのデータ提供をお待ちしています🙌 「まだまだ校章由来情報が埋まっていない高専があるので、情報提供頂けると助かります!」と告知をしてLTを締めました。 すると、早速神山まるごと高専さんが情報提供して下さりました。(圧倒的感謝) 高専かるた、さっそく神山まるごと高専も入れていただきありがとうございます🙌 少し長くなりますが、神山まるごと高専の由来は以下の通りです。… — 【公式】神山まるごと高専 (@kamiyama_kosen) 2023年11月20日 x.com 頂いた情報は温かいうちにオープンデータへ反映するようにするように努めております。 神山まるごと高専さんから頂いたデータも既に反映済みです。 校章由来情報を管理しているCSVは下記です。 皆さまからの温かい校章由来情報の提供を心よりお待ちしております! data/kosen_school_emblem.csv へのプルリクエストを作って頂いても、宮岸( @daiking1756 )へ直接ご連絡頂いてもどちらでも構いません🙌 本記事の公開時点では残り21高専分の由来情報が埋まっていない状態です。 github.com おわりに 本記事では高専オープンデータを利用して高専かるたを作った事例を紹介しました。 オープンデータは使い方次第で面白い作品がたくさん作れると思うので、 オープンデータ自体への貢献とそれを使った作品づくりを継続しようと思いました。 最後に告知です! NTTドコモグループでは高専出身者はもちろん、高専本科生の 採用 も行っております。 興味がある方はぜひエントリーしてみて下さい! また、 学生さん向けのイベント も随時開催中ですので、こちらもチェックしてみて下さい。 (今後もじゃんじゃん追加予定です) さらに!今年の年末12/26(火) 17:00 - 20:00で IoT縛りの勉強会! IoTLT vol.106@NTT Com(忘年会IoTLTラジオ) がNTT Comのオフィス内で開催されることになりました🎉 NTT Com社員はもちろん、一般の方も参加/登壇可能なイベントです。 現地参加枠には限りがあるので、興味のある方はお早めにご応募下さい! 当日の様子はYouTube Liveにて配信予定です。 iotlt.connpass.com それでは明日の記事もお楽しみに〜👋
アバター
この記事は、 NTT Communications Advent Calendar 2023 6日目の記事です。 こんにちは。 SDPF クラウド・仮想サーバーチームの杉浦 ( @Kumassy_ ) です。 普段は OpenStack の開発・運用をしており、最近は Observability まわりを取り組んでいます。 この記事では、以前私が Tech-Night という社内 LT 会で発表した以下のプロジェクトのご紹介します。 Tech-Night については以下の記事をご覧ください。 きっかけ 今年は不安定な世界情勢と円安、猛暑により電気代を気にする機会が多かったのではないでしょうか。 私もあるとき 7-9 月の電気代を確認したところ、電力使用量が 330 kWh、電気代が 10,000 円を超えていました。これは私のチームの 4 人家族のご家庭と比べても多い値でした。 なぜ私の家では電気代がかかってしまうのか? 私のチームはリモートワークが中心なため、働きやすいように冷房をつけっぱなしにしていました。エアコンが原因でしょうか。 それとも 24 時間ゲーミング PC を起動して Cookie Clicker を動かしているからでしょうか?今使っているゲーミング PC は Aura Sync 対応パーツで揃えて自作したものです。 ライティングが美しいので、消費電力は実質ゼロであり、電気代とは無関係なはずです。 自宅の消費電力を測定する 電気代をケチる前に、一体何が電力を消費しているのか測定したいところです。 はじめに検討した方法はワットモニターを使うことです。 瞬間的な消費電力を測定するのには向いていそうですが、 1 日の消費電力を時系列で確認するのはつらそうです。他にもっと安い製品もありそうですが、少し価格も高めです。 次にスマートプラグも検討しました。 スマートプラグは本来コンセントそのものを IoT 化するためのデバイスだと思いますが、消費電力を測定できる機能をもつ製品もあります。 これなら消費電力をグラフとして確認できるのでよさそうです。 ただ、エアコンや冷蔵庫等 1 つずつスマートプラグをつけようとすると高くなってしまいそうです。 また、風呂場の換気扇等、スマートプラグをつけられなさそうな電化製品の電力は測れなさそうです。 SwitchBot プラグミニ(JP) 電力会社はどのように電力使用量を測定しているか 昔は検針員 1 が各住宅を巡回し、電力使用量を確認していました。 今では、住宅にはスマートメーターという通信機能つきの電力計が設置されており、電力使用量が自動的に収集されています。 都市部ではスマートメーター同士が P2P で通信し、電力会社の端末までデータを転送しているそうです。面白いですね。 電力会社が使う通信路を A ルートと呼ぶそうです。 スマートメーターには通信機能が備わっていることがわかりました。 実は B ルートという方式を使えば、一般人でもスマートメーターから情報を取り出すことができます。 Wi-SUN モジュールを使ってスマートメーターとおしゃべりする スマートメーターとおしゃべりするには、 Wi-SUN という無線規格を使います。 Wi-SUN には低電力で長距離伝送できることとメッシュネットワークを構成できることが特徴とのこと。 Wi−SUN に対応した専用のモジュールはいくつかありますが、家に転がっていた Raspberry Pi を有効活用したかったので BP35A1 というモジュールを購入しました。 BP35A1 の他にも USB タイプの Wi-SUN モジュールもあるようなので、そちらのほうがお手軽かもしれません。 B ルートは暗号化されているため、 ID とパスワードを入手する必要があります。 東京電力管内であれば以下のサイトから ID とパスワードを確認できます。 ちなみに、 ID は郵便で送られてきます。 シリアル通信周りの設定をして、 Wi−SUN モジュールと Raspberry Pi を接続します。 メス-メスのジャンパ線が家になかったのでブレッドボードを介して繋げておきました。 スマートメーターにパケットを送るには、 B ルート ID とパスワードを設定 ネットワークをスキャン PANA 認証 ECHONET Lite 規格のパケットを送信 という手順を踏みます。 まずは B ルート ID を設定するため、 SKSETRBID <B ルート ID><CRLF> といったコマンドを Wi-SUN モジュールに送信します。 すると送信したコマンドのエコーバックと OK<CRLF> が返ってきます。 Wi−SUN モジュールからの応答が想定通りかどうかをチェックしたいところです。 瞬間消費電力のリクエストを投げるときはどうでしょうか。このときは SKSENDTO コマンドを使います。 レスポンスとしては ERXUDP <DATA><CRLF> が返ってくるので、これもバリデーションしたいです。 <DATA> は ECHONET Lite というプロトコルのバイナリ形式のデータです。 ECHONET Lite は仕様書が公開されており、以下のページから確認できます。 パーサーを書く さて、 Wi-SUN モジュールからの応答には OK<CRLF> のような ASCII 文字列とバイナリ形式のデータが混じっていることがわかりました。 これをうまくパースしてバリデーションをしたいのですが、どのようなコードを書けばよいでしょうか。 出力が ASCII 文字列であれば <CRLF> で文字列を区切ってしまえば簡単にパースできそうです。 そのような実装もあります 2 が、 <DATA> には <CRLF> に相当する \x13 や \x10 が含まれることがあり、私の環境ではうまく動きませんでした。 また、実験の結果レスポンスは 10 bytes ずつ返ってきたので、一時的に出力をバッファしておく必要があります。 パース処理はバッファに対して複数回試行されるので、パース処理が失敗したとしてもバッファの中身が変更されないようにする必要があります。 以上のような要件にあうパーサーのフレームワークを探したところ、 nom がよさそうでした。 説明書きには byte 列を食べて (bite) くれるといったことが書かれており、遊び心があってよいですね。 nom はパーサーコンビネータと呼ばれる種類のパーサーのようですが、パーサーコンビネータとはなんでしょうか。 パーサーコンビネータは小さいシンプルなパーサーを組み合わせて所望のパーサーを実現する方式です。 私は大学でコンパイラを作る授業を受けたのですが、そのときは lex と yacc を使って、 正規表現を書いて字句解析する BNF 記法で文法を定義する parser generator を使ってパーサーを生成する というトップダウン的なアプローチでパーサーを作っていました。 lex, yacc に渡すファイルの書式が独特なことと、文法を定義することが大変でやや苦労しました。 パーサーコンビネータはこれとは対象的に、小さなパーサーを組み合わせるボトムアップ的なアプローチでパーサーを作ります。 試しに OK<CRLF> をパースするパーサーを作ってみましょう。 OK という文字列をパースするには、 tag を、 <CRLF> をパースするには crlf を使います。 これらの間には他の文字は入らないので、 tuple を使ってこれらが連続して出てきたときにのみパースが成功するようにします。 簡単なパーサーなのに早くも tag と crlf という 2 つのパーサーを組み合わせてしまいました! 同様に IPv6 アドレスのパーサーを作ってみましょう。 Wi−SUN モジュールでの IPv6 アドレスは 0 を省略せず、次のようなフォーマットで表します。 take_while_m_n は条件式が成立する限り m 以上 n 以下の長さのバイト列を切り取ります。 OK パーサーと同様に tuple を使ってパーサーを組み合わせればよいです。 次に EVENT のパーサーを作ってみましょう。 EVENT のフォーマットは次のようになります。 EVENT <イベント番号> <IPv6アドレス> <パラメータ><CRLF> <パラメータ> の部分はイベント番号によって存在したりしなかったりします。 このようなときは opt コンビネータを使うことで、パースできなかったときに None を返すようにできます。 map_res はパースした結果を加工するコンビネータです。 21 というバイト列は ASCII コードから \x32\x31 と解釈されてしまうので、代わりに \x21 を得るために from_hex_u8 という自作の関数を適用します。 IPv6 アドレスのパースには先程作成した IPv6 パーサーがそのまま使えますね! さらに複雑なバイト列も、これまで書いてきたパーサーを組み合わせることでパースできます。 このように自作のパーサーを組み上げることで、パースできる対象が広がっていくのが面白いところです。 さて、ここまでは ASCII 文字列のバイト列を扱ってきましたが、それ以外のバイト列はどのように扱えばよいのでしょうか? tag の代わりに be_u8 などのパーサーが利用できます。 図 3-6 は ECHONET Lite プロトコルのパケットです。 OPC が要求数で、後ろに何個要求が含まれるかを表します。 まずは OPC の値を読み取るために be_u8 を使います。次に要求をパースするのですが、 count というコンビネータが便利です。これは引数に与えたパーサーを指定の回数適用し、結果を Vec にまとめて返してくれるコンビネータです。 各要求のパーサーを parse_edata_property として作成しておいたので、あとは count と組み合わせるだけですね。 同じ要領で Wi-SUN モジュールの応答をパースできるパーサーを用意しました。 あとはこれらを alt コンビネータに渡せば完成です。 alt は複数のパーサーを受け取り、最初に成功したパーサーを適用した結果を返すコンビネータです。 ということで、できました。 ソースコードは以下のページに置いてあります。 Raspberry Pi の OS 設定や配線、 Grafana Agent の設定方法も書いてあるので、よければ参考にしてみてください。 Grafana Agent は Exporter を Scrape して外部にメトリクスを送信してくれるエージェントです。 私の環境では、 Granafa Cloud に向けてメトリクスを送信するように Grafana Agent を設定してみました。 一日の消費電力を時系列で測定してみた結果 Grafana Cloud で作成したダッシュボードはこのような見た目になります。 この日は 10:30 頃にゲーミング PC の電源を入れたようです。他の家電製品は触っていないので、おそらくゲーミング PC のアイドル時の消費電力は 250 W 程度なのでしょう。 13:30 ごろに電子レンジを使って昼食を温めていたようです。 電子レンジの出力は 700 W のはずですが、ダッシュボードをみるに 1400 W 近く消費しているようです。 本当に 1400 W も消費しているのか疑わしいのでワットメーターを購入して検証してみたいところです。 消費電力がスパイクしている 21:00 ごろは、おそらく夕食を温めていたのでしょう。 夜間は重めの 3D ゲームで遊んでおり、 450 W ほど消費電力が増えています。 アイドル時の消費電力は 250W ほどだったので、このときのゲーミング PC は 700 W 消費している計算になるのです。 さて、ゲーミング PC のアイドル時の消費電力を 200 W として 24 時間稼働させたときの月当たりの消費電力は以下のようになります。 ここに、クッキー工場の経営者として不都合な真実が浮かび上がります。 ゲーミング PC を 24 時間くらいつけっぱなしにすると、月 144 kWh くらい消費しており、月間の消費電力の半分近くを占める計算です。 対策として、 Intel N100 チップを搭載したミニ PC を購入し、お財布及び環境に配慮した形でクッキーを焼くようにしました。 まとめ 今回の発表のまとめです。 今では自宅に設置されている電力計はスマートメーターという通信機能がついたものに置き換わっています。 B ルートという仕組みを使うことで、一般人でもスマートメーターから瞬間消費電力などの情報を取り出すことができます。 Wi−SUN モジュールとおしゃべりしたいときなど、なにかをパースしなければならないときはパーサーコンビネータのフレームワークを使ってみるものいいでしょう。 パーサーコンビネータは、小さいパーサーを組み合わせることで目的のテキストやバイナリ列をパースできるようにするボトムアップ的なアプローチをとるものでした。 Rust では nom が有名なので、検討してみるとよいでしょう。 最後に、クッキーを焼くときは電気代に注意し、 CPS (Cookie per Second) だけではなく CPW (Cookie per Watt) にも気を配るようにしましょう。 参考資料 B ルートの話 https://qiita.com/rukihena/items/82266ed3a43e4b652adb http://myama808.net/archives/16196021.html https://kitto-yakudatsu.com/archives/7206 https://rabbit-note.com/2016/12/25/bp35a1-python/ parser の話 https://hazm.at/mox/lang/rust/nom/index.html https://docs.rs/nom/7.1.3/nom/ https://shigoto.mhlw.go.jp/User/Occupation/Detail/74 にて仕事の内容が動画付きで紹介されている。 ↩ https://qiita.com/rukihena/items/82266ed3a43e4b652adb ↩
アバター
この記事は NTTコミュニケーションズ Advent Calendar 2023 の5日目の記事です。 こんにちは、イノベーションセンター所属の岩瀬( @iwashi86 )です。普段は生成AIチームのエンジニアリングマネジメントをしています。 この記事では「組織の遠心力」をテーマに組織を強くする方法について書いていきます。本記事を読むことで、組織改善策の一案が得られることを狙っています。 なお、本記事は一人のエンジニアリングマネージャーである @iwashi86 の主観を多く含みます。NTT Com内には多くの考え方があり、その1つとして受け取っていただければ幸いです。 組織の遠心力って何だろう? 同じ組織の @mizuman_ が社内講演した「最強のチームが最高のプロダクトを作る」というスライドがあります。 詳細は上記スライドをぜひご覧いただければと思いますが、チームが良ければ良いほど、プロダクト(やソリューション)の成功率が高まります。最強のチームを作るために、優秀なメンバーがチームに残り続けてくれる必要があります。(チームからメンバーが抜け続けていては、コミュニケーションの文脈が失われ続けるため、効果的に働くことが難しくなるため) 一方で厄介なことに組織には遠心力が働きます。遠心力とはその名の通りで、中心から外側に向けて遠ざかる力です。中心が会社そのものだとすると、個人が外側(つまり会社の外)に引っ張られる力を本記事では、「組織の遠心力」と呼んでいます。この遠心力が一定の(個人ごとに異なる)閾値を超えると、メンバーの離職につながります。 この遠心力はさまざまなレイヤーで存在します。すなわち、会社全体・組織(たとえば、部や部門)・チームといったレイヤーです。それぞれのレイヤーで異なる遠心力が働きます。例えば、チーム自体には愛着があるが、会社全体に対してはあまりエンゲージメントを感じない、といったようにレイヤーによって力の強さが異なります。 この各レイヤーの遠心力が大きくなりすぎると良くない結果(たとえばメンバーの離職など)につながります。さて、よくない結果を止めるためにはどうしたらいいのでしょうか? 私たちも究極な絶対解を持っている訳ではありません。ですが少なくとも、私たちの会社(NTTコミュニケーションズ)、組織(イノベーションセンター)ではこうしています、という取り組みがあります。以降では、遠心力の発生事由を考察した後に、遠心力を抑える取り組みについて紹介します。 なぜ、遠心力が生まれるのだろう? 遠心力が生まれる原因は、企業や組織によって大きく異なります。ここでは、典型的・ありがちな例を組織から見た内部要因と外部要因の2つに分けて紹介します。(NTT Comの例というよりは、一般論です) まず、組織の内部要因で言えば、次のような例があります。 組織のゴールがよくわからない そもそも、ゴールを理解するためのコミュニケーションが存在しない 入社前はフルリモートと聞いていたが、方針変更で出社強制となった 次に組織の外部要因で言えば、次のような例があります。 仕事以外のプライベートで、勤務可能環境が変わった SNSで見る情報から隣の芝生が青い(他社の環境が素晴らしく見える) ある個人の興味分野が全く異なったものに変わった 状況のように遠心力は内部要因と外部要因でさまざまな理由から生まれます。 内部要因・外部要因へのアプローチのスタンス では、遠心力を抑えるためにはどちらの要因にアプローチすれば良いのでしょうか? 結論から言えば、外部要因ではなく内部要因に集中してアプローチします。なぜ外部要因に対するアプローチはうまくいかないのか一例を挙げてみましょう。 外部的な要因は組織外にあるため、組織からは原則アンコントローラブルな領域です。無理やりコントロールしようとしても結果的にうまくいきません。 たとえば、アンチパターンの1つではありますが、仮に「隣の芝生の青さを見せないために、外部の勉強会に参加禁止」みたいなルールを設けたとしましょう。このルールが適用されると、外部勉強会の参加に価値を置いているメンバーのエンゲージメントがだだ下がりになります。勉強会の情報は、ソーシャルメディアで簡単に入手可能ですし、各種サービスのレコメンドアルゴリズムに自然と情報に触れてしまうことも多いでしょう。ルールで縛ったとしても、意味がないどころか悪影響なのです。 (補足:NTT Comは積極的に外に出ようというカルチャーがあります。特に、私の所属するイノベーションセンターには「枠を超えよう」という組織バリューがあり、社内外問わず、どんどん外に出るのが是というスタンスです) 内部要因へのアプローチ 外部要因へのアプローチの難しさがわかったので、内部要因へアプローチしていくことになります。以下で、企業・組織・チームのレイヤーごとのアプローチの具体事例を紹介していきます。 企業レイヤーでのアプローチ 企業全体のアプローチの一例としては、幹部と社員の対話会があります。NTT Comでも例に漏れず、 KURUMAZA.exe という幹部対話会を開催しています(リンク資料 P5~6参照)。幹部と直接対話することで、疑問に思っていた内容を解消できるので、自分の業務に納得感を得やすくなります。 なお、このKURUMAZA.exeは現在、リモート開催がメインですが、当初は車座になって幹部と話し合うという場をオフラインで作っていました。KURUMAZA で EXEcutive(幹部) と話し合うことから、KURUMAZA.exe というネーミングだったわけです。 すでに3年以上開催しており、このイベントの開催方法については @Mahito が 別記事 で説明しておりますので、ご興味あればぜひご覧ください。 組織レイヤーでのアプローチ 私の所属するイノベーションセンターでは、組織内のメンバーであれば誰でも参加できるIC酒場というものを開催しています。 元々はこの前身には、「 ICカタリバ 」というオンライン座談会がありました。新型コロナウイルス感染症の5類感染症移行に伴って、対面のイベントを開催できることになったので、試しにお酒もあり(飲まなくてもかまいません)な場を作ってはどうか、という案で始まったものです。終業後の時間帯から、任意で人が集まってワイワイしてチームの壁を越えてネットワークが形成されています。組織に所属する人を知り、話すことで組織への帰属感を高まります。(=遠心力の軽減につながります。) その他、業務面での戦略的なアプローチとしては、部門やプロジェクト横断でチームやプロジェクトを組成するというアプローチがあります。新しい人やチームに働きかけにいくというのは、どちらかというとハードルが高いと感じる人が多い印象です。何らかの理由があって話す機会があれば話せるのですが、そもそも理由がない状態から突撃するのは、一定の難易度があります。だから、業務である程度の強制力を持たせて、それを理由として使ってもらいます。すると意図的に組織の人的ネットワークを構築できるわけです。(マネージャー陣の腕の見せどころかもしれません) チームレイヤーでのアプローチ チームレベルで言えば、チームビルディングのアプローチがあります。チームビルディングに関しては、NTT Comで実際に使っているノウハウをまとめた チームビルディングハンドブック を公開しておりますので、詳細な方法はそちらをご確認ください。 その他、チーム単位でふりかえり(アジャイル開発でいうレトロスペクティブ)を実践する方法も有効です。ふりかえりの過程で自然と対話が生まれます。その対話を通じて、チームメンバー同士の相互理解が進み、チームの結束力が高まります。(=遠心力の軽減につながります。) 双方向と一方向のハイブリッド ここまで事例を含め、アプローチをいくつか紹介してきました。それら全てに共通するのは「双方向の対話」です。実際に双方向で話し合う機会を作ることで、遠心力を軽減できます。 もちろん対話以外にも、ドキュメントを使って考えを発信するような一方向のコミュニケーションもあります。発信が増えることで、組織内の情報の透明性が高まります。その結果、各メンバーの業務の背景理解につながり、業務の納得感の醸成につながるわけです。 したがって、組織の状態に応じてどちらも利用することで高い効果が得られます。 おわりに 本記事では組織の遠心力・その発生理由・私たちの組織で取り組む対応策についてご紹介しました。1つでも使ってみたいと思うネタが見つかれば幸いです。 それでは、明日の記事もお楽しみに!
アバター
この記事は、 NTT Communications Advent Calendar 2023 4日目の記事です。 この記事では、Web標準の仕様と実際のブラウザの挙動についての体験談を紹介します。 W3C(World Wide Web Consortium) は Web Standards というWebの標準仕様を制定しています。 この中でブラウザのWeb APIの挙動についても定義されています。 挙動が統一されていないなら別ですが、長く使われ標準化もされている技術において、すべてのモダンブラウザ 1 で挙動が同じ場合、それが仕様化された動作だと思うでしょう。 しかし、実際にはすべてのブラウザが同じ仕様違反をしているという例を WebRTC 2 で用いられる RTCPeerConnection を用いて説明します。 SDP 3 に手を入れているような開発者の方には特に興味深いかもしれません。 目次 目次 はじめに 経緯 WebRTCとSDPの補足 差分と仕様上の問題点 ブラウザの仕様違反 歴史的経緯 余談: 実際の仕様変更の過程について まとめ はじめに こんにちは、イノベーションセンター テクノロジー部門の池田です。 普段は SkyWay に関連して WebRTC やその次世代となる技術の調査や検証をしています。 この記事では、Web標準の仕様と実際の全モダンブラウザの挙動の違いについて、気付いた経緯や歴史的経緯などを紹介します。 経緯 気になったタイミングは読書会の中で WebRTC 1.0 APIの仕様 を読んでいて、その中でも 4.4.2 Interface Definition を読んでいる時でした。 そこで何度か読み直しても自身の知っているブラウザの挙動と仕様の手順が異なっていることに気づきました。 具体的には以下のようなコードの場合です。 const pc = new RTCPeerConnection(); const offer = await pc.createOffer(); offer.sdp = offer.sdp.replace( '<対象の文字列>' , '<置換後の文字列>' ); // sdpの更新 await pc.setLocalDescription(offer); // 仕様上InvalidModificationErrorになるはず 上のコードは3行目を除けば WebRTC を使う場合にブラウザで実行される一般的なコードです。 4 3行目の操作は SDP の文字列を書き換える処理 5 で、必要に応じて置換する文字列を変えます。 上のコードでの置換には意味はないですが、実際のアプリケーションで操作する際はAPIで制御できないようなところまで変更を加えたい際にこの手法が利用されます。 例えば 以前書いたLyraの利用の記事 でもLyraを使うための下準備として SDP を直接変更しています。 WebRTCとSDPの補足 WebRTC について知っている方は読み飛ばしてください。 ブラウザで WebRTC を用いた映像などのデータを送受信可能にするには事前にそれを可能にする情報を交換しておく必要があります。 SDP はこの情報交換のために利用されます。 各ブラウザはJavaScriptのAPIを使うことで自身の伝えるべき情報を SDP として準備し、情報交換に備えます。 ここに更なるカスタマイズをしたい場合には、上のコードのように SDP Munging をしたり、別のAPIで変更をしたりする必要があります。 差分と仕様上の問題点 上のコードは何が問題なのでしょうか? 2行目にある createOffer の final steps to create an offer のstep5には以下のようにあります。 Set the [[LastCreatedOffer]] internal slot to sdpString. これは [[LastCreatedOffer]] にこの関数で生成された SDP が格納されることを意味します。 そして、4行目にある setLocalDescription (以下sLD)のstep 4.2は以下のようにあります。 If type is "offer", and sdp is not the empty string and not equal to connection.[[LastCreatedOffer]], then return a promise rejected with a newly created InvalidModificationError and abort these steps. これは引数と [[LastCreatedOffer]] が異なる場合にRejectすることを指示しています。 しかし、前述のとおりこのコードは引数として異なる値を渡しているにも関わらず、全モダンブラウザでRejectされることなく動きます。 つまり、ブラウザの挙動と仕様が全く違います。 あまりにもその差が謎だったため、仕様を管理しているリポジトリに issueを作成 しました。 ブラウザの仕様違反 issue作成1時間程でいくつかコメントをいただきました。 その結果、仕様の読み取り方は正しく、ブラウザ側が揃って仕様違反であり、許可されていない操作であることが分かりました。 これは、以前は許可されていたが歴史的経緯によって禁止されたとのことでした。 歴史的経緯 昔はWebRTCのAPIがあまり存在せず、変更を加えるには SDP を修正する必要がありました。 SDP は何かしらのオブジェクトではなくただの文字列として記述されているため、 特定部分を変更するのが難しく、書き換える際も意図していない部分を書き換えないように気をつける必要があります。 また、行いたい変更を SDP の記法で書き下す必要があり、API以外に SDP のドメイン知識も求められることがより難易度を上げています。 例えば setCodecPreferences() というAPIの利用例の1つとして特定のコーデックのみを利用したい場合があります。これをAPIを用いずに手動で変更するには、 SDP の文字列から該当する部分を特定し、必要な部分だけを残すという処理を文字列の置換で行う必要があります。 その後WebRTCが発展すると、 MediaStreamTrack や RTCRtpTransceiver 単位での操作をするAPIが生まれ、より細かい修正をAPIでできるようになってきました。 APIの一例と先ほど出てきた setCodecPreferences() が挙げられています。 API経由での操作だと SDP を扱わなくてもやりたいことができるようになるため、より WebRTC の開発が容易になると思います。 このようにAPIで操作する/できるようになったため、仕様上では SDP の直接の変更が禁止されたのだと思います。 しかし、上に書いたLyraのケースのように、APIでは設定できない事項もまだ存在します。 そのため、WebRTC 1.0 APIを拡張する 拡張ユースケース などがAPIをさらに充実させ、APIの範囲を広げることが必要と感じました。 一方、今回紹介した setCodecPreferences() ですら下図のように現在は Firefoxで利用できない ため実際にAPIのみですべてが完了する世界はまだ遠いと感じます。 このような状態で SDP の変更が禁止されるとできることが制限されてしまうため、APIを補うために仕様上禁止されていても実際には変更を許容するのは仕方ないのかなと思います。 https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpTransceiver/setCodecPreferences より 余談: 実際の仕様変更の過程について 実際にいつ頃に SDP の変更が禁止されたのかをGitHubにあるリポジトリのコミットを追って調査しました。 細かい文言や章編成の変更コミットなどがあり、変更を追うのが大変でしたが、 変更が提案されたのは 2016/11のissue で、 実際に変更されたのは 2017/02のPR でした。 この変更は IETF97のスライド 内の Option D によるものらしいです。 この中で setLocalDescription() の引数は後方互換性のためとありますが、結果的には変更したSDPを適用するために未だに使われています。 まとめ 本記事ではWeb標準の仕様と実際のブラウザの挙動についての体験談を紹介しました。 ブラウザの統一された挙動が仕様通りとは限らないということが伝わったのではないかと思います。 逆に仕様を完全に理解しても、実際のブラウザの挙動が想定できるとは限らないのは辛い点だと感じました。 明日もお楽しみに。 ここではGoogle Chrome/Firefox/Safari/Edgeを指します。 ↩ とてもざっくりと説明するとブラウザ間で直接映像を送ることができる技術です。 ↩ 利用するIPアドレスやコーデックなどメディア送受信に必要な情報を交換するために用いるテキストデータです。 ↩ SkyWayではこの辺りのAPIがわからなくても利用できるようなラッパーになっています。 ↩ SDP Mungingとも呼ばれます。 ↩
アバター
この記事は、 NTT Communications Advent Calendar 2023 3日目の記事です。 はじめに みなさんこんにちは、イノベーションセンターの益本 (@masaomi346) です。 Network Analytics for Security (以下、NA4Sec) プロジェクトのメンバーとして、脅威インテリジェンス(潜在的な脅威について収集されたデータを収集・分析したもの)の分析業務をしています。 本記事では、日本を狙ったフィッシングサイトの情報配信をはじめたことについて紹介します。 セキュリティにおける情報配信について興味がある方、フィッシングについて興味がある方は、ぜひ最後まで読んでみてください。 NA4Secについて NA4Secは、「NTTはインターネットを安心・安全にする社会的責務がある」を理念として、インターネットにおける攻撃インフラの解明・撲滅を目指した活動をしているプロジェクトです。 また、NTT Comグループにおける脅威インテリジェンスチームとしての側面も持ち合わせており、有事において脅威インテリジェンスを提供し、意思決定を支援することもあります。 NTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズからもメンバーが参画しており、いろんな人が協力して、攻撃インフラを追跡しています。 本記事で紹介する内容は、NA4Secメンバーが過去に投稿した記事と関連しているので、ぜひ読んでみてください。 サイバー脅威インテリジェンス(CTI)配信はじめました Metemcyberについて Metemcyberは、食生活改善のような、セキュリティ運用の健全化を提供することを目標として活動しているプロジェクトです。 良質な脅威インテリジェンスをブロックチェーン上で流通させるための、脅威インテリジェンス流通基盤を開発しています。 プロジェクトの活動の詳細については、こちらのインターンシップの記事でも紹介しています。 インターンシップ体験記 〜セキュリティ運用の健全化を目指すMetemcyberの開発〜 また、NA4Secと兄弟関係にあるプロジェクトであり、Xのアカウント (@Metemcyber) でNA4Secと協力して、脅威インテリジェンス配信をしています。 フィッシングサイトの情報配信をはじめた経緯 私がNA4Secに加入してから、フィッシングに関する脅威インテリジェンスの分析をしてきました。 フィッシングを行っている攻撃者やフィッシングキットの分析などをしていましたが、社内で閉じずに、外部向けに何かフィッシングの脅威インテリジェンスの配信もしていきたいと考えていました。 そこで、こちらのマルウェアの情報配信のときと同様、Metemcyberアカウントでのフィッシング情報配信をやっていくことになりました。 サイバー脅威インテリジェンス(CTI)配信はじめました より多くの脅威情報を配信しつづけることで、多くの人に活用してもらえるようなり、インターネットの安全に貢献できればという思いで始めました。 投稿の目的・実現したいこと フィッシングサイトの報告件数は年々増加しており、被害も増加しています。 フィッシングサイトの情報発信をしていくことで、サービス事業者などでフィッシング対策している担当者の方々に、活用してもらえるようになることを目的としています。 そして、フィッシング詐欺の被害の減少に少しでも貢献していくことで、NA4Secの理念でもある「NTTはインターネットを安心、安全にする社会的責務がある」を実現していきます。 実際に配信してみる 🚨⚡ #Phishing #フィッシング詐欺 #フィッシング (🇯🇵) Brand: #SMBC #三井住友 IP: 🌍 192.252.189[.]72 (ASN:AS64050) URL: 🎣 hxxps://www.eovmfheuk9810.com/ 🎣 hxxps://www.igiaplfel5936.com/ 🎣 hxxps://www.mtmckoycfqd190.com/ 🎣 hxxps://www.tkurmciuvdq150.com/ H/T to Team NA4Sec pic.twitter.com/plqwdwZpPk — Metemcyber (@Metemcyber) 2023年11月20日 配信内容は以下のようになっています。 フィッシングのハッシュタグ ターゲットになっているブランド名 IPアドレス URL フィッシングサイトのスクリーンショット 配信内容(投稿フォーマット)で工夫したこと 活用してもらいやすくするためには、必要な情報をぱっと見でわかりやすくまとめなくてはいけません。 どういった情報を記載するかについては、フィッシングサイトの情報配信をしている方々の投稿内容を参考にしました。 また、視認性を上げるために絵文字を使ったり、スクリーンショットを添付したりしています。 警告 → パトランプ(🚨)と雷(⚡️)の絵文字 国 → 国旗(🇯🇵)の絵文字 IPアドレス → 地球(🌍)の絵文字 URL → 釣竿(🎣)の絵文字 今後の取り組みとフィードバックについて フィッシングサイトの情報配信で得られた知見を使って、新たなインテリジェンスを生み出していきたいと思います。 まだまだ情報配信を始めたばかりであるため、改良しつつ、フィッシングサイトの情報配信を続けていきます。 そのためにも、みなさんの意見を取り入れていきたいと思っています。 発信内容やフォーマットについてフィードバックしたい方、これ以外に何かお話ししたいことがある方は、 公式アカウント (@Metemcyber) もしくは、私のアカウント (@masaomi346) までご連絡ください。 さいごに 本記事では、日本を狙ったフィッシングサイトの情報配信をはじめたことについて紹介しました。 今後もさまざまな脅威インテリジェンスを発信していきます。 公式アカウント (@Metemcyber) をフォローしていただけると幸いです。 宣伝 NA4Sec/Metemcyberチームではそれぞれ一緒に働く仲間を募集しています。 脅威インテリジェンスに興味があり、プロジェクトの理念に共感していただける方は、ぜひ応募してみてください。 NA4Sec (Threat Intelligence Analyst / 脅威インテリジェンスアナリスト) Metemcyber (Threat Intelligence Engineer / 脅威インテリジェンスエンジニア) また、学生向けのインターンシップでは、以下のようなことを実施しました。 NA4Sec 攻撃者はいかにしてフィッシングサイトを隠すか?(インターンシップ体験記) インターンシップ体験記 〜Cobalt StrikeのC2サーバ追跡〜 Metemcyber インターンシップ体験記 〜セキュリティ運用の健全化を目指すMetemcyberの開発〜 学生の方で、NA4Sec/Metemcyberチームでのインターンシップに興味を持った方は、次回のインターンシップに参加してみてください。 最後まで、ご覧頂きありがとうございました! それでは、明日の記事もお楽しみに!
アバター
この記事は、 NTT Communications Advent Calendar 2023 3日目の記事です。 はじめに みなさんこんにちは、イノベーションセンターの益本 (@masaomi346) です。 Network Analytics for Security (以下、NA4Sec) プロジェクトのメンバーとして、脅威インテリジェンス(潜在的な脅威について収集されたデータを収集・分析したもの)の分析業務をしています。 本記事では、日本を狙ったフィッシングサイトの情報配信をはじめたことについて紹介します。 セキュリティにおける情報配信について興味がある方、フィッシングについて興味がある方は、ぜひ最後まで読んでみてください。 NA4Secについて NA4Secは、「NTTはインターネットを安心・安全にする社会的責務がある」を理念として、インターネットにおける攻撃インフラの解明・撲滅を目指した活動をしているプロジェクトです。 また、NTT Comグループにおける脅威インテリジェンスチームとしての側面も持ち合わせており、有事において脅威インテリジェンスを提供し、意思決定を支援することもあります。 NTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズからもメンバーが参画しており、いろんな人が協力して、攻撃インフラを追跡しています。 本記事で紹介する内容は、NA4Secメンバーが過去に投稿した記事と関連しているので、ぜひ読んでみてください。 サイバー脅威インテリジェンス(CTI)配信はじめました Metemcyberについて Metemcyberは、食生活改善のような、セキュリティ運用の健全化を提供することを目標として活動しているプロジェクトです。 良質な脅威インテリジェンスをブロックチェーン上で流通させるための、脅威インテリジェンス流通基盤を開発しています。 プロジェクトの活動の詳細については、こちらのインターンシップの記事でも紹介しています。 インターンシップ体験記 〜セキュリティ運用の健全化を目指すMetemcyberの開発〜 また、NA4Secと兄弟関係にあるプロジェクトであり、Xのアカウント (@Metemcyber) でNA4Secと協力して、脅威インテリジェンス配信をしています。 フィッシングサイトの情報配信をはじめた経緯 私がNA4Secに加入してから、フィッシングに関する脅威インテリジェンスの分析をしてきました。 フィッシングを行っている攻撃者やフィッシングキットの分析などをしていましたが、社内で閉じずに、外部向けに何かフィッシングの脅威インテリジェンスの配信もしていきたいと考えていました。 そこで、こちらのマルウェアの情報配信のときと同様、Metemcyberアカウントでのフィッシング情報配信をやっていくことになりました。 サイバー脅威インテリジェンス(CTI)配信はじめました より多くの脅威情報を配信しつづけることで、多くの人に活用してもらえるようなり、インターネットの安全に貢献できればという思いで始めました。 投稿の目的・実現したいこと フィッシングサイトの報告件数は年々増加しており、被害も増加しています。 フィッシングサイトの情報発信をしていくことで、サービス事業者などでフィッシング対策している担当者の方々に、活用してもらえるようになることを目的としています。 そして、フィッシング詐欺の被害の減少に少しでも貢献していくことで、NA4Secの理念でもある「NTTはインターネットを安心、安全にする社会的責務がある」を実現していきます。 実際に配信してみる 🚨⚡ #Phishing #フィッシング詐欺 #フィッシング (🇯🇵) Brand: #SMBC #三井住友 IP: 🌍 192.252.189[.]72 (ASN:AS64050) URL: 🎣 hxxps://www.eovmfheuk9810.com/ 🎣 hxxps://www.igiaplfel5936.com/ 🎣 hxxps://www.mtmckoycfqd190.com/ 🎣 hxxps://www.tkurmciuvdq150.com/ H/T to Team NA4Sec pic.twitter.com/plqwdwZpPk — Metemcyber (@Metemcyber) 2023年11月20日 配信内容は以下のようになっています。 フィッシングのハッシュタグ ターゲットになっているブランド名 IPアドレス URL フィッシングサイトのスクリーンショット 配信内容(投稿フォーマット)で工夫したこと 活用してもらいやすくするためには、必要な情報をぱっと見でわかりやすくまとめなくてはいけません。 どういった情報を記載するかについては、フィッシングサイトの情報配信をしている方々の投稿内容を参考にしました。 また、視認性を上げるために絵文字を使ったり、スクリーンショットを添付したりしています。 警告 → パトランプ(🚨)と雷(⚡️)の絵文字 国 → 国旗(🇯🇵)の絵文字 IPアドレス → 地球(🌍)の絵文字 URL → 釣竿(🎣)の絵文字 今後の取り組みとフィードバックについて フィッシングサイトの情報配信で得られた知見を使って、新たなインテリジェンスを生み出していきたいと思います。 まだまだ情報配信を始めたばかりであるため、改良しつつ、フィッシングサイトの情報配信を続けていきます。 そのためにも、みなさんの意見を取り入れていきたいと思っています。 発信内容やフォーマットについてフィードバックしたい方、これ以外に何かお話ししたいことがある方は、 公式アカウント (@Metemcyber) もしくは、私のアカウント (@masaomi346) までご連絡ください。 さいごに 本記事では、日本を狙ったフィッシングサイトの情報配信をはじめたことについて紹介しました。 今後もさまざまな脅威インテリジェンスを発信していきます。 公式アカウント (@Metemcyber) をフォローしていただけると幸いです。 宣伝 NA4Sec/Metemcyberチームではそれぞれ一緒に働く仲間を募集しています。 脅威インテリジェンスに興味があり、プロジェクトの理念に共感していただける方は、ぜひ応募してみてください。 NA4Sec (Threat Intelligence Analyst / 脅威インテリジェンスアナリスト) Metemcyber (Threat Intelligence Engineer / 脅威インテリジェンスエンジニア) また、学生向けのインターンシップでは、以下のようなことを実施しました。 NA4Sec 攻撃者はいかにしてフィッシングサイトを隠すか?(インターンシップ体験記) インターンシップ体験記 〜Cobalt StrikeのC2サーバ追跡〜 Metemcyber インターンシップ体験記 〜セキュリティ運用の健全化を目指すMetemcyberの開発〜 学生の方で、NA4Sec/Metemcyberチームでのインターンシップに興味を持った方は、次回のインターンシップに参加してみてください。 最後まで、ご覧頂きありがとうございました! それでは、明日の記事もお楽しみに!
アバター
この記事は、  NTT Communications Advent Calendar 2023  2日目の記事です。 こんにちは、イノベーションセンターの坪井です。 1日目の記事を担当した平木と同じくNetwork Analytics for Securityというチーム(通称NA4Sec)に所属しています。 1日目の記事はこちらです。 https://engineers.ntt.com/entry/2023/12/01/102753 engineers.ntt.com NA4Secプロジェクトについては、  サイバー脅威インテリジェンス(CTI)配信はじめました  を読んでいただくと我々がどんな活動を行なっているかわかると思います。 先日の11/21(火)にInternet Week 2023の C10 DNS DAY というプログラムの中で「ランダムサブドメイン攻撃において事業者として行なった対策と解析について」というタイトルで講演をさせていただきました。 講演の中で、私はDNSハニーポットを運用してランダムサブドメイン攻撃を観測した話をさせていただいたのですが、アドベントカレンダー2日目の本記事では講演の中で出てきたサブドメイン列挙ツールについて、お話しします。 はじめに:免責事項 ランダムサブドメイン攻撃とは サブドメイン列挙とは SubBruteについて サブドメイン列挙とランダムサブドメイン攻撃 まとめ はじめに:免責事項 本記事は、あくまでも調査したサブドメイン列挙ツールの動きについてご紹介するものであり、利用を積極的に推奨するものではありません。また、これらのツールを悪意ある目的で使用することは禁止されています。 これらのツールを使用することによって発生したいかなる損害や問題について、ツール開発者や提供者及び本記事の執筆者・ブログ管理者は責任を負いません。予めご了承ください。 ランダムサブドメイン攻撃とは まず初めに、ランダムサブドメイン攻撃について簡単に説明します。 ランダムサブドメイン攻撃とはサイバー空間におけるDNSを使ったDDoS攻撃手法の1つです。 悪意のある攻撃者がランダムな文字列やパターンを使用して、特定のドメイン名に対して無差別にサブドメイン名を生成し、生成したサブドメイン名を使ってキャッシュDNSサーバに名前解決の問い合わせ(クエリ)をします。 キャッシュDNSサーバでは受けたクエリのドメイン名に対する情報をすでに保持している場合は保持している情報を返しますが、保持していない場合はそのドメイン名の権威DNSサーバにクエリをします 1 。 今回のようなランダムなサブドメイン名の場合、ほとんどのドメイン名についてはキャッシュDNSサーバには過去のクエリ応答情報が存在していないため、攻撃者からのクエリの都度、権威DNSサーバへクエリをすることになります。 一般的にランダムサブドメイン攻撃は、意図的に大量のDNSクエリを発生させて権威DNSサーバへ負荷をかけることで、各種リソースの逼迫にともなう関連サービスの品質低下またはサーバダウンなどによる関連サービスの停止(いわゆるDoS)を狙いとした攻撃と考えられています。 サブドメイン列挙とは 次に、今回のテーマであるサブドメイン列挙ツールが行う、サブドメイン列挙についてお話しします。 サブドメイン列挙(Subdomain Enumeration)とは、特定のドメインに関して利用されているサブドメイン 2 を調査し、そのドメイン名をリスト化するプロセスです。 サブドメイン列挙ツールは、セキュリティテストやネットワークスキャニング、ペネトレーションテスト、またはウェブアプリケーションのセキュリティ評価など、さまざまなセキュリティアクティビティで使用されます。 サブドメイン列挙ツールは以下のような手法を使用してサブドメインを収集します。 有名なサブドメイン列挙ツールについて、いくつか取り上げて比較してみました。 サブドメイン列挙ツールはどういう仕組みになっているか、今回はInternet Weekの講演の中でも名前が出たSubBruteについて中身を見ていきたいと思います。 SubBruteについて SubBruteはPythonで書かれたオープンソースのサブドメイン列挙ツールで主にブルートフォースを実行することに特化しています。DNS におけるブルートフォースは一般的なサブドメインの名前や文字列の組み合わせなどのパターンをさまざま試行する手法です。 指定されたドメインに対して複数の一般的なサブドメイン名を含むワードリストを使用し、DNSクエリを送信して有効なサブドメインを探索します。このプロセスにより、目標のドメインに紐づく未知のサブドメインを見つけることができます。 TheRook/subbrute: A DNS meta-query spider that enumerates DNS records, and subdomains. (github.com) SubBruteのプログラムを紐解いてみたところ、下記の機能を備えていることがわかりました。 なお、下記の機能名は処理内容から類推して付けた呼び名なので、実際の機能名とは関係ありません。 ターゲットドメイン情報取得機能 サブドメイン作成対象のターゲットドメインの権威DNSサーバを列挙し、応答性のテストを行い、正常応答の場合はDNSクエリ対象の権威DNSサーバ一覧に追加する。 サブドメインリスト生成機能 一般的に使用されるサブドメイン(wwwなど)や予め用意されたワードリストを使用して存在する可能性のあるサブドメインリストを生成し、DNSクエリをランダムな順序で行えるようにリストの順序をシャッフルさせる。権威DNSサーバがDNSクエリを異常とみなすことを防いだり、1つの権威DNSサーバに対するDNSクエリが集中することを避けることが目的と思われる。 DNSクエリ実行機能 サブドメインリスト生成機能で生成されたリストを元にDNSクエリを行う。この機能はマルチプロセスで実行され、大量のDNSクエリを並列で実行する。 DNSクエリ結果解析機能 DNSクエリ実行機能の結果から必要な情報を取り出し解析・整形する。 Aレコードの場合はドメイン名に対応するIPv4アドレスを、AAAAレコードの場合はIPv6アドレスを取り出す。CNAMEレコードからは別名となるドメイン名を取り出す。 取り出した結果をドメイン名と対応するIPアドレスで組み合わせた形に整形する。 例として example.jp をターゲットドメインとしてSubBruteを実行すると以下のような動きになります。 $ python subbrute.py example.jp メイン機能で example.jp をターゲットのドメインとして設定する。 ターゲットドメイン情報取得機能で指定されたターゲットドメイン  example.jp  のDNSクエリに応答する権威DNSサーバーのリストを生成する。 サブドメインリスト生成機能で、ターゲットドメイン  example.jp  のすべての可能性のあるサブドメインのリストを作成しリスト内をシャッフル。 DNSクエリ実行機能で、 example.jp  の各サブドメインに対するDNSクエリを並列で実行する。 DNSクエリ結果解析機能で、DNSクエリ実行機能の結果を解析・整形する。例えば  mail.example.jp www.example.jp  などの  example.jp  のサブドメイン名と、サブドメイン名に対応するIPアドレスを出力する。 以上の流れにより、 example.jp  のサブドメインと、それらのドメイン名に紐づくIPアドレスなどの情報を取得できます。 このようにワンライナーのコマンドでサブドメイン列挙ツールを実行するだけでサブドメイン列挙行為自体はとても簡単に行うことができます。 サブドメイン列挙とランダムサブドメイン攻撃 さて、今回はSubBruteの動作を紐解いてみました。SubBruteの動作の中では大量のサブドメイン候補からなるリストを生成し、DNSクエリを行っています。 「SubBruteについて」内では説明を省略していますが、SubBruteがDNSクエリを実行する際は、予め設定されているキャッシュDNSサーバを使用します。 キャッシュDNSサーバへクエリした時に、キャッシュDNSサーバがクエリ応答情報を保持していないサブドメインだった場合、権威DNSサーバにクエリを行うことになります。 あれ、これ何かに似てませんか? そう、冒頭で説明したランダムサブドメイン攻撃の構造に似ているんです。 サブドメイン列挙ツールはセキュリティテストやネットワークスキャニング、ペネトレーションテスト、またはウェブアプリケーションのセキュリティ評価といったシチュエーションでの利用を想定して開発されたツールですが、その動作過程上でDNSクエリを大量発生させ、ランダムサブドメイン攻撃に似た状況を作り出してしまうことがあるため、ツールの取り扱いには十分注意するようにしましょう。 まとめ 今回はInternet Weekで登壇させていただいた講演内容からサブドメイン列挙ツールについて深掘りして書いてみました。改めて、サブドメイン列挙とランダムサブドメイン攻撃は紙一重と感じました。最後まで、ご覧頂きありがとうございました。 明日も同じくNetwork Analytics for Securityチームに所属する益本の記事です!それでは、明日の記事もお楽しみに! 厳密にはいきなり目的の権威DNSサーバにクエリをする挙動をとるとは限りませんが、最終的には何らかの形で権威DNSサーバへのクエリにつながるので、ここでは簡単のためにこのように表現しています。 ↩ あるドメインの中の部分的な名前空間のことで、そのドメインに関連する特定のサービス(ウェブサイトやメールなど)を提供するサーバを識別するためなどに使われる。例えば「www」はウェブサーバを指す名前としてよく使われる。 ↩
アバター
この記事は、  NTT Communications Advent Calendar 2023  2日目の記事です。 こんにちは、イノベーションセンターの坪井です。 1日目の記事を担当した平木と同じくNetwork Analytics for Securityというチーム(通称NA4Sec)に所属しています。 1日目の記事はこちらです。 engineers.ntt.com NA4Secプロジェクトについては、  サイバー脅威インテリジェンス(CTI)配信はじめました  を読んでいただくと我々がどんな活動を行なっているかわかると思います。 先日の11/21(火)にInternet Week 2023の C10 DNS DAY というプログラムの中で「ランダムサブドメイン攻撃において事業者として行なった対策と解析について」というタイトルで講演をさせていただきました。 講演の中で、私はDNSハニーポットを運用してランダムサブドメイン攻撃を観測した話をさせていただいたのですが、アドベントカレンダー2日目の本記事では講演の中で出てきたサブドメイン列挙ツールについて、お話しします。 はじめに:免責事項 ランダムサブドメイン攻撃とは サブドメイン列挙とは SubBruteについて サブドメイン列挙とランダムサブドメイン攻撃 まとめ はじめに:免責事項 本記事は、あくまでも調査したサブドメイン列挙ツールの動きについてご紹介するものであり、利用を積極的に推奨するものではありません。また、これらのツールを悪意ある目的で使用することは禁止されています。 これらのツールを使用することによって発生したいかなる損害や問題について、ツール開発者や提供者及び本記事の執筆者・ブログ管理者は責任を負いません。予めご了承ください。 ランダムサブドメイン攻撃とは まず初めに、ランダムサブドメイン攻撃について簡単に説明します。 ランダムサブドメイン攻撃とはサイバー空間におけるDNSを使ったDDoS攻撃手法の1つです。 悪意のある攻撃者がランダムな文字列やパターンを使用して、特定のドメイン名に対して無差別にサブドメイン名を生成し、生成したサブドメイン名を使ってキャッシュDNSサーバに名前解決の問い合わせ(クエリ)をします。 キャッシュDNSサーバでは受けたクエリのドメイン名に対する情報をすでに保持している場合は保持している情報を返しますが、保持していない場合はそのドメイン名の権威DNSサーバにクエリをします 1 。 今回のようなランダムなサブドメイン名の場合、ほとんどのドメイン名についてはキャッシュDNSサーバには過去のクエリ応答情報が存在していないため、攻撃者からのクエリの都度、権威DNSサーバへクエリをすることになります。 一般的にランダムサブドメイン攻撃は、意図的に大量のDNSクエリを発生させて権威DNSサーバへ負荷をかけることで、各種リソースの逼迫にともなう関連サービスの品質低下またはサーバダウンなどによる関連サービスの停止(いわゆるDoS)を狙いとした攻撃と考えられています。 サブドメイン列挙とは 次に、今回のテーマであるサブドメイン列挙ツールが行う、サブドメイン列挙についてお話しします。 サブドメイン列挙(Subdomain Enumeration)とは、特定のドメインに関して利用されているサブドメイン 2 を調査し、そのドメイン名をリスト化するプロセスです。 サブドメイン列挙ツールは、セキュリティテストやネットワークスキャニング、ペネトレーションテスト、またはウェブアプリケーションのセキュリティ評価など、さまざまなセキュリティアクティビティで使用されます。 サブドメイン列挙ツールは以下のような手法を使用してサブドメインを収集します。 有名なサブドメイン列挙ツールについて、いくつか取り上げて比較してみました。 サブドメイン列挙ツールはどういう仕組みになっているか、今回はInternet Weekの講演の中でも名前が出たSubBruteについて中身を見ていきたいと思います。 SubBruteについて SubBruteはPythonで書かれたオープンソースのサブドメイン列挙ツールで主にブルートフォースを実行することに特化しています。DNS におけるブルートフォースは一般的なサブドメインの名前や文字列の組み合わせなどのパターンをさまざま試行する手法です。 指定されたドメインに対して複数の一般的なサブドメイン名を含むワードリストを使用し、DNSクエリを送信して有効なサブドメインを探索します。このプロセスにより、目標のドメインに紐づく未知のサブドメインを見つけることができます。 TheRook/subbrute: A DNS meta-query spider that enumerates DNS records, and subdomains. (github.com) SubBruteのプログラムを紐解いてみたところ、下記の機能を備えていることがわかりました。 なお、下記の機能名は処理内容から類推して付けた呼び名なので、実際の機能名とは関係ありません。 ターゲットドメイン情報取得機能 サブドメイン作成対象のターゲットドメインの権威DNSサーバを列挙し、応答性のテストを行い、正常応答の場合はDNSクエリ対象の権威DNSサーバ一覧に追加する。 サブドメインリスト生成機能 一般的に使用されるサブドメイン(wwwなど)や予め用意されたワードリストを使用して存在する可能性のあるサブドメインリストを生成し、DNSクエリをランダムな順序で行えるようにリストの順序をシャッフルさせる。権威DNSサーバがDNSクエリを異常とみなすことを防いだり、1つの権威DNSサーバに対するDNSクエリが集中することを避けることが目的と思われる。 DNSクエリ実行機能 サブドメインリスト生成機能で生成されたリストを元にDNSクエリを行う。この機能はマルチプロセスで実行され、大量のDNSクエリを並列で実行する。 DNSクエリ結果解析機能 DNSクエリ実行機能の結果から必要な情報を取り出し解析・整形する。 Aレコードの場合はドメイン名に対応するIPv4アドレスを、AAAAレコードの場合はIPv6アドレスを取り出す。CNAMEレコードからは別名となるドメイン名を取り出す。 取り出した結果をドメイン名と対応するIPアドレスで組み合わせた形に整形する。 例として example.jp をターゲットドメインとしてSubBruteを実行すると以下のような動きになります。 $ python subbrute.py example.jp メイン機能で example.jp をターゲットのドメインとして設定する。 ターゲットドメイン情報取得機能で指定されたターゲットドメイン  example.jp  のDNSクエリに応答する権威DNSサーバーのリストを生成する。 サブドメインリスト生成機能で、ターゲットドメイン  example.jp  のすべての可能性のあるサブドメインのリストを作成しリスト内をシャッフル。 DNSクエリ実行機能で、 example.jp  の各サブドメインに対するDNSクエリを並列で実行する。 DNSクエリ結果解析機能で、DNSクエリ実行機能の結果を解析・整形する。例えば  mail.example.jp www.example.jp  などの  example.jp  のサブドメイン名と、サブドメイン名に対応するIPアドレスを出力する。 以上の流れにより、 example.jp  のサブドメインと、それらのドメイン名に紐づくIPアドレスなどの情報を取得できます。 このようにワンライナーのコマンドでサブドメイン列挙ツールを実行するだけでサブドメイン列挙行為自体はとても簡単に行うことができます。 サブドメイン列挙とランダムサブドメイン攻撃 さて、今回はSubBruteの動作を紐解いてみました。SubBruteの動作の中では大量のサブドメイン候補からなるリストを生成し、DNSクエリを行っています。 「SubBruteについて」内では説明を省略していますが、SubBruteがDNSクエリを実行する際は、予め設定されているキャッシュDNSサーバを使用します。 キャッシュDNSサーバへクエリした時に、キャッシュDNSサーバがクエリ応答情報を保持していないサブドメインだった場合、権威DNSサーバにクエリを行うことになります。 あれ、これ何かに似てませんか? そう、冒頭で説明したランダムサブドメイン攻撃の構造に似ているんです。 サブドメイン列挙ツールはセキュリティテストやネットワークスキャニング、ペネトレーションテスト、またはウェブアプリケーションのセキュリティ評価といったシチュエーションでの利用を想定して開発されたツールですが、その動作過程上でDNSクエリを大量発生させ、ランダムサブドメイン攻撃に似た状況を作り出してしまうことがあるため、ツールの取り扱いには十分注意するようにしましょう。 まとめ 今回はInternet Weekで登壇させていただいた講演内容からサブドメイン列挙ツールについて深掘りして書いてみました。改めて、サブドメイン列挙とランダムサブドメイン攻撃は紙一重と感じました。最後まで、ご覧頂きありがとうございました。 明日も同じくNetwork Analytics for Securityチームに所属する益本の記事です!それでは、明日の記事もお楽しみに! 厳密にはいきなり目的の権威DNSサーバにクエリをする挙動をとるとは限りませんが、最終的には何らかの形で権威DNSサーバへのクエリにつながるので、ここでは簡単のためにこのように表現しています。 ↩ あるドメインの中の部分的な名前空間のことで、そのドメインに関連する特定のサービス(ウェブサイトやメールなど)を提供するサーバを識別するためなどに使われる。例えば「www」はウェブサーバを指す名前としてよく使われる。 ↩
アバター
この記事は、 NTT Communications Advent Calendar 2023 1日目の記事です。 はじめに こんにちは、イノベーションセンターの平木と申します。 11月1日にNA4Secプロジェクト 1 のチームにセキュリティエンジニアとしてjoinしまして、急遽、エンジニアブログに投稿させていただくことになりました。 今日ご紹介したいのは、前職(NTT Comの他部門)のセキュリティ機器の導入プロジェクトの話で、その中で私が遭遇した「嘘のような本当の話!?」をご紹介し、そこで得た学びをお伝えしたいと思います。 開発プロジェクトの概要 とある事件をきっかけに全社的にセキュリティ意識が今まで以上に高まって、より適切に権限をコントロールすべく、認可認証の仕組みが導入されることが決まりました。我々のチームでは、サーバネットワーク基盤を用意し、認証アプリを導入し、運用を確立することがミッションでした。そして私は、主にシステム構築のプロジェクトマネージャとして、スケジューリングから設計、ベンダコントロールなどを推進しました。 このプロジェクトの中で私が担当した業務の1つがセキュリティポリシーの設計です。そして、策定したポリシーの1つが「Firewallポリシーを許可リスト方式にする」ことでした。 許可リスト方式とは、通過させる送信元/宛先通信の対象をリスト形式で指定し、それ以外の通信を遮断する方式です。 例えば「送信元IPアドレスが192.0.2.1、宛先ポートがTCP22番ポート(SSH)となる通信」「宛先IPアドレスが198.51.100.2、宛先ポートがTCP443番ポート(HTTPS)となる通信」..といった形で対象となる通信をリスト形式で指定します。 許可リスト方式を用いることで、通信を必要最小限の経路に限定し、意図しない侵入経路の発生を防ぎます。 対照的に、遮断する通信を指定し、それ以外の通信を通過させる方式を拒否リスト方式と呼びます。 なぜ許可リスト形式としたか?部門内の他システムでは許可対象選定が甘く、不要な通信を含む/24や/20などの大きめのネットワークが許可され、結果的に内部ネットワークで重要なサーバへの通信が全許可されてしまっていたケースが過去に発生していました。そのため、必要な通信のみを許可して欲しいと考え、敢えて許可リスト形式という形を強調し開発を進めました。 加えて、本システムは認証認可だけではなく、CLIやGUI操作のログを収集する機能も持っており、複数のネットワークが接続される環境でした。 万が一不正侵入された場合には、複数ネットワークをまたがる中継点として悪用されかねない 2 ため、一般的なシステムよりも高いセキュリティレベルが求められます。 そのために、EDR(Endpoint Detection and Response)や多要素認証に加えて、許可リスト方式でアクセス可能なホストをより厳格に制限することでセキュリティを強化することとしました。 そして事件は起こった! まさかの全送信元IP許可 たまたま設計を見直す機会があって、許可リスト設定を確認したところ、なんとクライアントネットワークと一部のサーバネットワークについて、全送信元IPアドレスからの通信が許可されていることに気づきました。これでは、過去に重要なサーバへの通信が許可されてしまったことの反省が活かされていないことになってしまいます。なお今思い返すと、設定変更に気づける仕組みがあれば良かったと思いますが、構築開始当初で十分に仕組みやプロセスが整っておらず、当時は気づくことができませんでした。現在は運用プロセスで気づけるようになっています。 なぜ許可してしまったのか? 状況を確認したところ「クライアントネットワークと一部のサーバネットワークではIPアドレスを固定できないホストがおり、当該ネットワークの全IPアドレスから通信を許可せざるをえなかった」ということが分かりました。 結局どうしたか? 暫定対処としては、全許可によるリスクを改めて評価し、対象となるネットワークはインターネット公開システムを持っていない点等を考慮し、直接侵入されるリスクが低いことから、リスクを許容することになりました。 本格対処としては、許可リストの目的・意図を明文化し、セキュリティポリシーを関係者で改めて共有しました。 ただし、そもそもの話として、許可リストが適切だったか?という点は再考すべきであったかもしれません。システムを導入しようとしたネットワークは、過去の経緯が積み重なった結果、構成が不明瞭となり、そもそもIPアドレスの精査が難しかったとも考えられます。当然この状況で精査をしようとしても、精査の負荷が大きくなるため、運用負荷を軽減すべくセキュリティポリシーを緩めざるを得なかったと捉えることができます。 今回の場合、境界型防御と一部、ゼロトラスト・アーキテクチャの思想を取り入れたハイブリット設計を採用しましたが、「ネットワークの場所だけで無条件に信頼しない」というゼロトラストの考え方をさらに推し進めて、IPアドレスに依存しないセキュリティコントロールを前提に設計した方が、もしかしたら既存システムやその運用プロセスにマッチする形で本来実現したかったセキュリティに近づけられたかもしれません。 学び 現行ネットワークに即したセキュリティ要件にすることの大事さ 許可リスト方式は設計する上では分かりやすいが、管理コストの高さなど負の側面もあるので導入に際しては慎重に検討を 理想的には、ゼロトラストの考え方を推し進めたIPアドレスに依存しないセキュリティコントロールの方が、運用しやすかった可能性がある 今回、たまたま初期構築のタイミングで、運用が整っていない中で、不適切な設定が入ってしまったという状況でした。なお、本件以降は、正式な運用が立ち上がり、第三者の目で許可リストを精査するプロセスが入ったため、同様の事象は起こっていません。そのため、許可リスト方式を採用するのであれば、このようにプロセスで防ぐ形が効果的だと考えています。 まとめ この記事では、前職での開発経験の反省を、セキュリティにフォーカスした形で書かせていただきました。 NA4Secプロジェクトでは前職の経験も生かしつつ、分析業務にもチャレンジする予定です。 次は現職で分析したノウハウや経験、成果を、エンジニアブログに書いていきたいと思いますのでご期待ください。 最後まで、ご覧頂きありがとうございました!それでは、明日の記事もお楽しみに! NA4Secプロジェクトについては、このブログの記事 サイバー脅威インテリジェンス(CTI)配信はじめました をご覧ください。 ↩ このようにシステム内部に不正侵入した後に、そこを足がかりに他のサーバなどに侵入を広げる活動を「ラテラルムーブメント」と言います。 ↩
アバター
この記事は、 NTT Communications Advent Calendar 2023 1日目の記事です。 はじめに こんにちは、イノベーションセンターの平木と申します。 11月1日にNA4Secプロジェクト 1 のチームにセキュリティエンジニアとしてjoinしまして、急遽、エンジニアブログに投稿させていただくことになりました。 今日ご紹介したいのは、前職(NTT Comの他部門)のセキュリティ機器の導入プロジェクトの話で、その中で私が遭遇した「嘘のような本当の話!?」をご紹介し、そこで得た学びをお伝えしたいと思います。 開発プロジェクトの概要 とある事件をきっかけに全社的にセキュリティ意識が今まで以上に高まって、より適切に権限をコントロールすべく、認可認証の仕組みが導入されることが決まりました。我々のチームでは、サーバネットワーク基盤を用意し、認証アプリを導入し、運用を確立することがミッションでした。そして私は、主にシステム構築のプロジェクトマネージャとして、スケジューリングから設計、ベンダコントロールなどを推進しました。 このプロジェクトの中で私が担当した業務の1つがセキュリティポリシーの設計です。そして、策定したポリシーの1つが「Firewallポリシーを許可リスト方式にする」ことでした。 許可リスト方式とは、通過させる送信元/宛先通信の対象をリスト形式で指定し、それ以外の通信を遮断する方式です。 例えば「送信元IPアドレスが192.0.2.1、宛先ポートがTCP22番ポート(SSH)となる通信」「宛先IPアドレスが198.51.100.2、宛先ポートがTCP443番ポート(HTTPS)となる通信」..といった形で対象となる通信をリスト形式で指定します。 許可リスト方式を用いることで、通信を必要最小限の経路に限定し、意図しない侵入経路の発生を防ぎます。 対照的に、遮断する通信を指定し、それ以外の通信を通過させる方式を拒否リスト方式と呼びます。 なぜ許可リスト形式としたか?部門内の他システムでは許可対象選定が甘く、不要な通信を含む/24や/20などの大きめのネットワークが許可され、結果的に内部ネットワークで重要なサーバへの通信が全許可されてしまっていたケースが過去に発生していました。そのため、必要な通信のみを許可して欲しいと考え、敢えて許可リスト形式という形を強調し開発を進めました。 加えて、本システムは認証認可だけではなく、CLIやGUI操作のログを収集する機能も持っており、複数のネットワークが接続される環境でした。 万が一不正侵入された場合には、複数ネットワークをまたがる中継点として悪用されかねない 2 ため、一般的なシステムよりも高いセキュリティレベルが求められます。 そのために、EDR(Endpoint Detection and Response)や多要素認証に加えて、許可リスト方式でアクセス可能なホストをより厳格に制限することでセキュリティを強化することとしました。 そして事件は起こった! まさかの全送信元IP許可 たまたま設計を見直す機会があって、許可リスト設定を確認したところ、なんとクライアントネットワークと一部のサーバネットワークについて、全送信元IPアドレスからの通信が許可されていることに気づきました。これでは、過去に重要なサーバへの通信が許可されてしまったことの反省が活かされていないことになってしまいます。なお今思い返すと、設定変更に気づける仕組みがあれば良かったと思いますが、構築開始当初で十分に仕組みやプロセスが整っておらず、当時は気づくことができませんでした。現在は運用プロセスで気づけるようになっています。 なぜ許可してしまったのか? 状況を確認したところ「クライアントネットワークと一部のサーバネットワークではIPアドレスを固定できないホストがおり、当該ネットワークの全IPアドレスから通信を許可せざるをえなかった」ということが分かりました。 結局どうしたか? 暫定対処としては、全許可によるリスクを改めて評価し、対象となるネットワークはインターネット公開システムを持っていない点等を考慮し、直接侵入されるリスクが低いことから、リスクを許容することになりました。 本格対処としては、許可リストの目的・意図を明文化し、セキュリティポリシーを関係者で改めて共有しました。 ただし、そもそもの話として、許可リストが適切だったか?という点は再考すべきであったかもしれません。システムを導入しようとしたネットワークは、過去の経緯が積み重なった結果、構成が不明瞭となり、そもそもIPアドレスの精査が難しかったとも考えられます。当然この状況で精査をしようとしても、精査の負荷が大きくなるため、運用負荷を軽減すべくセキュリティポリシーを緩めざるを得なかったと捉えることができます。 今回の場合、境界型防御と一部、ゼロトラスト・アーキテクチャの思想を取り入れたハイブリット設計を採用しましたが、「ネットワークの場所だけで無条件に信頼しない」というゼロトラストの考え方をさらに推し進めて、IPアドレスに依存しないセキュリティコントロールを前提に設計した方が、もしかしたら既存システムやその運用プロセスにマッチする形で本来実現したかったセキュリティに近づけられたかもしれません。 学び 現行ネットワークに即したセキュリティ要件にすることの大事さ 許可リスト方式は設計する上では分かりやすいが、管理コストの高さなど負の側面もあるので導入に際しては慎重に検討を 理想的には、ゼロトラストの考え方を推し進めたIPアドレスに依存しないセキュリティコントロールの方が、運用しやすかった可能性がある 今回、たまたま初期構築のタイミングで、運用が整っていない中で、不適切な設定が入ってしまったという状況でした。なお、本件以降は、正式な運用が立ち上がり、第三者の目で許可リストを精査するプロセスが入ったため、同様の事象は起こっていません。そのため、許可リスト方式を採用するのであれば、このようにプロセスで防ぐ形が効果的だと考えています。 まとめ この記事では、前職での開発経験の反省を、セキュリティにフォーカスした形で書かせていただきました。 NA4Secプロジェクトでは前職の経験も生かしつつ、分析業務にもチャレンジする予定です。 次は現職で分析したノウハウや経験、成果を、エンジニアブログに書いていきたいと思いますのでご期待ください。 最後まで、ご覧頂きありがとうございました!それでは、明日の記事もお楽しみに! NA4Secプロジェクトについては、このブログの記事 サイバー脅威インテリジェンス(CTI)配信はじめました をご覧ください。 ↩ このようにシステム内部に不正侵入した後に、そこを足がかりに他のサーバなどに侵入を広げる活動を「ラテラルムーブメント」と言います。 ↩
アバター
この記事では社内部署横断で開催したデータ分析開発合宿の概要や様子を紹介します。 目次 目次 はじめに データ分析開発合宿とは なぜやろうと思ったのか データ分析開発合宿の流れ 開催の様子 Step1 キックオフ Step2 課題ヒアリング Step3 合宿 データ分析開発合宿で得られた成果 おわりに はじめに 皆さんこんにちは、クラウド&ネットワークサービス部の丹野と、ソリューションサービス部の小関と是松です。 私たちは普段業務の傍ら、「データサイエンスちゃんねる」という社内のデータ分析コミュニティの運営をしており、社内向けの輪読会やKaggle LT会などを企画、開催しています。 この記事では、データサイエンスちゃんねる運営主催によるデータ分析開発合宿の概要や様子、成果について紹介したいと思います。 データ分析開発合宿とは データ分析開発合宿とは、エンジニアを中心とした社員が集まって自社サービスの実データ分析を短期集中で行うイベントです。 今回が初開催だったのですが、業務の異なる社内6部署から総勢25名が集まりました。 今回は部署間の交流を目的として全6つの混合チームを作成しました。 本企画では、一般的なデータ分析コンペのように事前に決められた課題を解いて競う形式ではなく、課題のヒアリングから始まる、より実際の分析業務に近い形式となっています。 自ら仮説を立て、最終的にサービスの課題解決に向けて具体的に提案することが目標になります。 なぜやろうと思ったのか 「データサイエンスちゃんねる」運営には社内のさまざまな部署に所属するメンバーが参加しているのですが、ある時以下のように各部署でデータ分析に関する多種多様な課題、ミッション、想いを持っているという話があがりました。 例えば、サービス開発部署は「ログデータをもっと活用してサービス改善に生かしたい」、お客さまにサービスの提案や受託分析を行う部署は「自社サービスのさらなる理解や分析人材を増やしていきたい」、データ分析人材の育成をミッションにしている部署は「自分たちの専門的な分析ノウハウを社内に普及させていきたい」というような形です。 これに対して、運営内でどうすれば上記のミッションや課題解決を達成できるか議論を重ねました。 その結果、データ分析に挑戦したい人から育成ができる人までさまざまな分析人材を一堂に集め、社内ログデータの分析に取り組んでもらう本合宿のコンセプトが決まりました そこから合宿の企画内容の詳細を詰めていき、複数のサービス担当者の方へログデータ提供を依頼しました。 また参加者を募集するにあたり、部署横断での宿泊を伴う企画は前例があまりなかったため、各部署の責任者の方に対して施策内容と期待される効果の説明を行い、本企画への賛同と参加者募集の協力を依頼しました。 その結果3つのサービス(NeWork、SDPF、Node-AI)の協力が得られ、参加者も集まり、開催に至ることができました。 データ分析開発合宿の流れ 企画は次の3Stepで実施しました。 各チームは分析に取り組む社内のサービスを1つ選びます。(Step1) 各チームは選択した各サービス担当者へ課題をヒアリングします。(Step2) 各チームはサービスの担当者から提供を受けたログデータを集中的に分析し、課題の解決策を提案します。(Step3) 開催の様子 Step1 キックオフ Step1では各チームでの顔合わせや、合宿の概要について説明を実施しました。 オンラインだと初めて会う人同士でお互いの理解や議論が深まりにくいという課題もあります。そこで、初回はオフィスに集合して開催することで、チームの結束を高め、Step1終了以降で各チームが実施するオンラインでのグループワークがスムーズに進むようにプログラムしました。 チームの顔合わせでは、これまでの自分の活動を年表形式で共有する自分史紹介を企画しました。各自が自分史を説明しながらの自己紹介で少しずつ打ち解けて、笑顔が出て盛り上がっている雰囲気が伝わってきました。 また、分析対象となる3サービス(NeWork、SDPF、Node-AI)のデータの提供と、各サービス担当者が持つ課題についての説明を行いました。 各チームはこの後、ログデータの分析を始め、課題を読み解いていきながら、自チームで分析を担当するサービスを1つ決めていきます。 また、折角オフラインで集まったため、Step1終了後は懇親会を開催しました。積極的なコミュニケーションがなされ、お互いへの理解もさらに深まった様子で、対面開催の良さを感じました。 Step2 課題ヒアリング Step2では各サービス担当者の方へのヒアリングを実施してもらいました。 分析対象サービスの担当者に対してデータやサービスの内容についての質問や、課題を深掘るためのヒアリングを実施してもらいました。 ヒアリングはオンライン開催であり、サービス担当者のヒアリング時の手間をなるべく小さくするため、質問表は参加者全体で共有し、同じ質問がでないように工夫しました。また、ヒアリング時の動画を録画し、ヒアリングに参加できない方と参加された方で情報の偏りが発生しないようにしました。 Step3 合宿 Step3では、2泊3日でホテルに篭り、分析と分析結果をもとにどんな施策を提言できるかの議論を進めてもらい、最終日には成果報告会を実施しました。 成果報告会はオンラインとオフラインの融合で開催しました。サービス担当者や社内でデータ分析に興味のある方にもオンラインで参加してもらいました。そして、各チームからログデータの分析結果の報告と、分析をもとに各サービスの課題に対してどのような施策を打つと良いかをサービス担当者へ向けて提案してもらいました。逆にサービス担当者からは提案に対してのフィードバックをもらいました。双方向のコミュニケーションを通じて、各チームには自分たちの分析と提案への自信をつけてもらい、サービス担当者にはデータへのさらなる理解と気づきを得てもらうことが狙いです。 質問やコメントも活発に飛び交い良い相乗効果が得られたと思いました。 データ分析開発合宿で得られた成果 合宿参加者の声を一部抜粋して掲載します。 社内にデータサイエンスに興味のある知り合いが増えて、今後の連携に有効に人脈が活用できそうでその点はとても満足している 実験やコンペのために用意されたデータではなく、プロダクトの実データを用いてデータ分析をする機会がモテたことで実践的なデータ分析のスキルが上がったと感じる また、サービス担当者の声も一部抜粋して掲載します。 データ提供するサービスの主幹として参加させていただきました。主幹で気づいていなかった分析結果・仮説を提示いただき、非常に参考になりました。また発表会で紹介いただいた分析の進め方や仮説の立て方は、今後主幹でデータ活用する際の参考にさせていただきます。 普段開発者として時間的にもスキル的にも実施が難しいようなユーザ利用状況分析をしていただけてありがたかった。 おわりに 今回のデータ分析開発合宿実施によって、社内の部署が抱えていたログデータの分析によるサービス改善と、分析者のスキル向上に関する課題解決に加えて、部門を超えた人材交流にも貢献ができました。 今後も今回のような合宿企画を通して、社内のデータ分析を盛り上げていければと思っています。 (2024/04/04 追記)この記事の続編として、この合宿で実際に行った分析とその結果をまとめています。こちらからご覧ください。 データ分析開発合宿を開催しました~自社サービス改善のためのデータ分析事例紹介~ NTTコミュニケーションズでは現在、冬季インターンを募集しています。 現場受け入れ型インターンシップではAIエンジニア・データサイエンティストのワークフィールドで合計8ポストを用意しております。特にNo.22、No.23のポストは我々「データサイエンスちゃんねる」運営も所属しているチームでの業務を体験できます。当社の活動に興味を持っていただいた方、私たちと一緒に働いてみませんか? ドコモグループ ウィンターインターンシップ2023 ( ※現場受け入れ型インターンシップは締め切り間際のためご注意ください! 募集は終了しました)
アバター
こんにちは、イノベーションセンターの加藤です。普段はコンピュータビジョンの技術開発やAI/機械学習(ML: Machine Learning)システムの検証に取り組んでいます。一方で、兼務 1 で大規模言語モデル(LLM: Large Language Model)について調査を行なっており、特にLLMの推論や学習の高速化に関心を持っています。 今回は、小さな言語モデルによる先読みを活用してLLMの文章生成を高速化する手法(Assisted Generation 2 , Speculative Sampling 3 などと呼ばれています)についてご紹介します。 LLMの推論は計算コストが高く、文章生成の遅さが課題としてよく挙げられています。特に日本語はトークンあたりの文字数が少なく、ChatGPTのようなストリーム出力でもかなり生成が遅く感じるかと思います。 これに対して、いくらか余分にメモリを利用して、元々のLLMと全く同じものをより高速に出力できるAssisted Generationが提案されています。AI開発・機械学習のためのプラットフォームを運営する Hugging Face が公開している機械学習ライブラリであるTransformersでも実装されています。この記事ではその手法についてまとめ、より発展的な手法とともにHugging Faceのモデルで実験しました。 LLMの文章生成メカニズム モデルの推論速度とAssisted Generation generate()の高速化 sample()の高速化 トークナイザの互換性 更なる高速化: Token Tree Verification LLMの文章生成メカニズム② Causal Language ModelにおけるAttention Maskについて Token Tree Verificationを用いたgenerate() 元論文の実装 おわりに LLMの文章生成メカニズム 文章生成に使われるLLMの多くはCausal Language Modelで作られています。Causal Language Modelとは単語列(厳密にはトークン列)が与えられた時にのちに続く単語(トークン)を予測するモデルで、与えられたトークン列 に対し、各 の次に来るべきトークン の分布 を出力します。 以下の例では、入力文"The man worked as a"の各トークンに対して、 facebook/opt-125m モデルを用いて最も次に来そうなトークンを予測した結果を図示しています。一度の推論で全トークンが並列に予測できていることや、Causal Language Modelでは後ろの入力トークンが前の予測トークンに影響しないことが今回紹介する技術のポイントです。 これを用いると1回の推論で新しいトークンを1つ(ここではsecurity)生成できるため、何度も繰り返すことで文章を生成します。 これをAutoregressive Generationと呼び、Transformersでは generate() という関数が提供されています。特にこの例のように最も確率の高いものを次のトークンに選択する方法をGreedy Decodingと呼びます。 (結局最終トークンの予測 "security" しか使い道が無いように見えますが、残りのトークンの予測も後述の手法で活用できます。) import torch import transformers from transformers import AutoModelForCausalLM, AutoTokenizer print (torch.__version__) # 2.1.0+cu121 print (transformers.__version__) # 4.34.1 model = AutoModelForCausalLM.from_pretrained( "facebook/opt-125m" ).cuda() tokenizer = AutoTokenizer.from_pretrained( "facebook/opt-125m" , use_fast= False ) prompt = "The man worked as a" input_ids = tokenizer(prompt, return_tensors= "pt" ).input_ids.cuda() print (tokenizer.decode(model.generate(input_ids, max_new_tokens= 10 )[ 0 ], skip_special_tokens= True )) # The man worked as a security guard at a hotel in the city of K また、LLMが出力した確率に沿って出力をサンプリング sample() することで、多様性のある文章を生成することもできます。 print (tokenizer.decode(model.generate(input_ids, max_new_tokens= 10 , do_sample= True )[ 0 ], skip_special_tokens= True )) print (tokenizer.decode(model.generate(input_ids, max_new_tokens= 10 , do_sample= True )[ 0 ], skip_special_tokens= True )) # The man worked as a clerk to a bakery. He worked for a bakery # The man worked as a janitor on a cleaning staff in our house. モデルの推論速度とAssisted Generation 最近公開されているLLMは10億~1000億規模のパラメータを持っており、推論に非常に時間がかかることが知られています。 A100 GPUで facebook/opt モデルを動作させ、ランダムな文章( C4データセット の冒頭部を利用)の続きを推論させた場合は以下の図のようになり、13Bモデルは最軽量の125Mモデルの30倍の生成時間がかかります。13B, 125Mはモデルのパラメータ数を表しており、それぞれ13 billion、125 millionを示しています。 そこで1回の推論で各位置の次トークンを並列に予測できるという性質を利用し、小さなモデルでいくつか「先読み」してからLLMでそれを検証するAssisted Generationという手法で高速に文章を生成できます。以下の節ではその手法を説明します。 generate()の高速化 まず小さなモデル(以降ではドラフトモデルと呼びます)で新しい単語を複数生成します。この時使用するモデルはLLMよりも軽量でかつ同じトークンIDを使っている必要があります。以下の例では1.3Bモデルに対し、同じトークンIDを使っていてより軽量な125Mモデルをドラフトモデルにしています。 from transformers import AutoModelForCausalLM, AutoTokenizer, GenerationConfig import torch assist = AutoModelForCausalLM.from_pretrained( "facebook/opt-125m" ).cuda() model = AutoModelForCausalLM.from_pretrained( "facebook/opt-1.3B" , torch_dtype=torch.float16).cuda() tokenizer = AutoTokenizer.from_pretrained( "facebook/opt-1.3B" , use_fast= False ) prompt = "The man worked as a" input_ids = tokenizer(prompt, return_tensors= "pt" ).input_ids.cuda() initial_len = input_ids.size( 1 ) for i in range ( 10 ): with torch.no_grad(): y = assist(input_ids) next_id = y.logits[:, - 1 , :].argmax(- 1 , keepdims= True ) input_ids = torch.cat([input_ids, next_id], dim=- 1 ) # 入力文章 + [ドラフトモデルが生成した文章] print (prompt + f "[{tokenizer.decode(input_ids[0,initial_len:], skip_special_tokens=True)}]" ) # The man worked as a[ security guard at a hotel in the city of K] 次に生成した文章 "The man worked as a security guard at a hotel..." をLLMに入れ、各位置の次トークンを予測します。 with torch.no_grad(): y = model(input_ids) next_ids = y.logits.argmax(- 1 ) for i in range ( 10 ): input_words = tokenizer.decode(input_ids[ 0 , :initial_len+i], skip_special_tokens= True ) next_words = tokenizer.decode(next_ids[ 0 , initial_len+i- 1 :initial_len+i], skip_special_tokens= True ) print (f "model[{i}]: {input_words}[{next_words}]" ) if next_ids[ 0 , initial_len+i- 1 ] != input_ids[ 0 , initial_len+i]: assist_input = tokenizer.decode(input_ids[ 0 , :initial_len+i], skip_special_tokens= True ) assist_word = tokenizer.decode(input_ids[ 0 , initial_len+i:initial_len+i+ 1 ], skip_special_tokens= True ) print (f "assistant: {assist_input}[{assist_word}]" ) break """ model[0]: The man worked as a[ security] model[1]: The man worked as a security[ guard] model[2]: The man worked as a security guard[ at] model[3]: The man worked as a security guard at[ the] assistant: The man worked as a security guard at[ a] """ LLMの予測トークンが "security guard at" まではドラフトモデルの出力と一致していることがわかります。そこでこの一致した分とその次のLLMの予測 "the" を合わせた "security guard at the" をAssisted Generationの出力として一気に採用してしまいます。 Greedy Decodingの性質上、LLMで初期プロンプトから1つずつトークンを生成しても、初めて予測がずれたこの"the"まで一致することが以下のように確かめられます。 prompt = "The man worked as a" input_ids = tokenizer(prompt, return_tensors= "pt" ).input_ids.cuda() initial_len = input_ids.size( 1 ) for i in range ( 10 ): with torch.no_grad(): model_inputs = model.prepare_inputs_for_generation(input_ids) y = model(**model_inputs) next_id = y.logits[:, - 1 , :].argmax(- 1 , keepdims= True ) input_ids = torch.cat([input_ids, next_id], dim=- 1 ) print (tokenizer.decode(input_ids[ 0 ], skip_special_tokens= True )) # The man worked as a security guard at the University of California, Berkeley, # "The man worked as a security guard at the" が model[3] と一致している つまり1回のLLM推論と10回のドラフトモデル推論で4トークン生成できました。これを繰り返すことで巨大なモデルの推論回数を節約しながらそのモデルの出力を完全に再現できます。これはTransformersでも assistant_model を引数に指定することで利用できます。 facebook/opt のさまざまなパラメータ数のモデルに対し125Mモデル( facebook/opt-125m )を用いてAssisted Generationを行った際の実行時間は以下のようになります。 モデル規模の差が大きければ大きいほどAssisted Generationの恩恵を受けられることが分かります。 sample()の高速化 サンプリングにドラフトモデルを活用する際は、先読みで得られた確率分布を用いて棄却サンプリングを行い、LLMの出力する確率分布を再現します。棄却サンプリングとは、確率分布が非常に複雑でそこからのサンプリングが困難である時に使われる手法で、目的の確率分布 に対してサンプリングしやすい確率分布 を用意して次のように行います。 (前準備) 任意の に対し が成り立つような定数 を用意する。(用意できない場合は棄却サンプリングを使えない。すなわち が定義されている では常に も定義されている必要があり、さらに確率比 は有界である必要がある。) からサンプリングする 一様乱数からのサンプリング ] が を満たせば を からのサンプリング結果として採用、そうでなければ棄却し 2. からやり直す。 これにより から直接サンプリングすることなく、その確率分布に従う を生成できます。この手法の弱点の1つに、用意した があまりに大きいと棄却の割合が増えサンプリングの効率が落ちることが挙げられます。 ただし一般的に使われている棄却サンプリングの前提とは異なり、LLMの確率分布からのサンプリングも先読みが終わる度に計算できるため、次のような工夫がなされています。 入力トークン に対し、ドラフトモデルで 個先までの確率分布 を先読みする 先読みしたトークンを用いてLLMの確率分布 を計算する(一度の推論で可能) に対して、一様乱数 ] が を満たせば採用、そうでなければ棄却する 初めて棄却された のトークンを を正規化した分布からサンプリングする に を加え, 1.に戻る この手法を用いることによって、1ループあたり1単語を必ず生成でき、また棄却サンプリングの仮定である の定義域や確率比の上界などを気にする必要がなくなります。 実装のソースコードはこちら from typing import Dict import torch from transformers import TemperatureLogitsWarper, LogitsProcessorList from transformers.generation.stopping_criteria import StoppingCriteriaList class AssistMixin : def draft ( self, eos_token_id: int , input_ids: torch.LongTensor, max_assistant_tokens: int , do_sample: bool , ) -> torch.LongTensor: draft_ids = input_ids self.cache[ "assistant_prob_list" ] = [] for idx in range (max_assistant_tokens): if "assistant_past_key_values" in self.cache: prev_seq_len = self.cache[ "assistant_past_key_values" ][ 0 ][ 0 ].shape[- 2 ] # `new_token_len` can be 1 or 2 (next token in assistant + last token picked by the larger model) new_token_len = draft_ids.shape[ 1 ] - prev_seq_len assist_inputs = draft_ids[:, -new_token_len:] assist_attn = torch.ones_like(draft_ids) assistant_model_outputs = self.assist_model( assist_inputs, attention_mask=assist_attn, past_key_values=self.cache[ "assistant_past_key_values" ]) else : assistant_model_outputs = self.assist_model(draft_ids) self.cache[ "assistant_past_key_values" ] = assistant_model_outputs.past_key_values assist_new_logits = assistant_model_outputs.logits[:, - 1 , :] assist_new_logits = self.logits_processor(draft_ids, assist_new_logits) assist_new_logits = self.logits_warper(draft_ids, assist_new_logits) if do_sample: assist_new_probs = assist_new_logits.softmax(- 1 ) self.cache[ "assistant_prob_list" ].append(assist_new_probs) new_token = torch.multinomial(assist_new_probs, num_samples= 1 ).squeeze( 1 ) else : new_token = assist_new_logits.argmax(- 1 ) draft_ids = torch.cat((draft_ids, new_token[:, None ]), dim=- 1 ) if new_token[ 0 ] == eos_token_id: break if do_sample: self.cache[ "assistant_prob_list" ] = torch.stack(self.cache[ "assistant_prob_list" ], dim= 1 ) return draft_ids def verify ( self, eos_token_id: int , input_ids: torch.LongTensor, candidate_input_ids: torch.LongTensor, max_len: int , do_sample: bool , ) -> torch.LongTensor: candidate_length = candidate_input_ids.shape[ 1 ] - input_ids.shape[ 1 ] cur_len = input_ids.shape[ 1 ] if "past_key_values" in self.cache: model_attn = torch.ones_like(candidate_input_ids) model_input_ids = candidate_input_ids[:, -candidate_length- 1 :] outputs = self.model( model_input_ids, attention_mask=model_attn, past_key_values=self.cache[ "past_key_values" ], ) else : outputs = self.model(candidate_input_ids) logits = outputs.logits for i in range (candidate_length): logits[:, i, :] = self.logits_processor(candidate_input_ids[:, :cur_len + i], logits[:, i, :]) for i in range (candidate_length): logits[:, i, :] = self.logits_warper(candidate_input_ids[:, :cur_len + i], logits[:, i, :]) speculative_ids = candidate_input_ids[:, -candidate_length:] if do_sample: probs = logits[:, -candidate_length- 1 :, :].float().softmax(- 1 ) speculative_probs = self.cache[ "assistant_prob_list" ].gather(dim=- 1 , index=speculative_ids[:,:, None ]).squeeze(- 1 ) speculative_actual_probs = probs[:, :- 1 , :].gather(dim=- 1 , index=speculative_ids[:,:, None ]).squeeze(- 1 ) resample_probs = probs.clone() resample_probs[:, :- 1 , :] = torch.clamp(resample_probs[:, :- 1 , :] - self.cache[ "assistant_prob_list" ].float(), min = 0 ) resample_probs /= resample_probs.sum(- 1 , keepdim= True ) acceptance_thresholds = speculative_actual_probs / speculative_probs unif = torch.rand_like(acceptance_thresholds) n_matches = ((~(unif <= acceptance_thresholds)).cumsum(dim=- 1 ) < 1 ).sum() else : selected_tokens = logits[:, -candidate_length- 1 :, :].argmax(- 1 ) n_matches = ((~(speculative_ids == selected_tokens[:,:- 1 ])).cumsum(dim=- 1 ) < 1 ).sum().cpu().item() n_matches = min (max_len - cur_len, n_matches) self.cache[ "matches" ].append(n_matches) self.cache[ "past_key_values" ] = outputs.past_key_values input_ids = torch.cat((input_ids, speculative_ids[:, :n_matches]), dim=- 1 ) if input_ids[ 0 , - 1 ] == eos_token_id or input_ids.shape[ 1 ] == max_len: # if EOS or max_len, STOP return input_ids # add one more token if do_sample: return torch.cat((input_ids, torch.multinomial(resample_probs[:, n_matches, :], num_samples= 1 )), dim=- 1 ) else : return torch.cat((input_ids, selected_tokens[:, n_matches:n_matches+ 1 ]), dim=- 1 ) def crop_cache (self, assist_input_ids, large_input_ids): # Discard past key values relative to unused assistant tokens self.cache[ "past_key_values" ] = tuple ([( kv[ 0 ][:, :, :large_input_ids.shape[ 1 ]- 1 , :], kv[ 1 ][:, :, :large_input_ids.shape[ 1 ]- 1 , :], ) for kv in self.cache[ "past_key_values" ]]) self.cache[ "assistant_past_key_values" ] = tuple ([( kv[ 0 ][:, :, :assist_input_ids.shape[ 1 ]- 1 , :], kv[ 1 ][:, :, :assist_input_ids.shape[ 1 ]- 1 , :], ) for kv in self.cache[ "assistant_past_key_values" ]]) class SpecSampler (AssistMixin): def __init__ (self, tokenizer, large_model, assist_model): self.tokenizer = tokenizer self.model = large_model self.assist_model = assist_model @torch.no_grad() def generate (self, input_ids: torch.LongTensor, max_new_len: int , temperature: float ): max_len = input_ids.shape[ 1 ] + max_new_len self.cache = {} self.cache[ "matches" ] = [] self.max_assistant_tokens = 5 self.logits_processor = LogitsProcessorList() self.logits_warper = LogitsProcessorList([TemperatureLogitsWarper(temperature)]) while True : draft_ids = self.draft(self.tokenizer.eos_token_id, input_ids, max_assistant_tokens=self.max_assistant_tokens, do_sample= True ) new_input_ids = self.verify(self.tokenizer.eos_token_id, input_ids, draft_ids, max_len=max_len, do_sample= True ) n_matches = new_input_ids.shape[ 1 ] - input_ids.shape[ 1 ] - 1 if n_matches == self.max_assistant_tokens: self.max_assistant_tokens += 2 else : self.max_assistant_tokens = max ( 1 , self.max_assistant_tokens - 1 ) input_ids = new_input_ids self.crop_cache(input_ids[:, :- 1 ], input_ids) if input_ids.shape[ 1 ] >= max_len or input_ids[ 0 , - 1 ] == self.tokenizer.eos_token_id: break return input_ids こちらもTransformersでは assistant_model を引数に指定することで利用できますが、バージョン4.34.1時点では若干効率が悪い実装になっています。 というのもTransformers実装では先読みの確率分布のSoftmax温度を常に0にしているのと等価なものになっており、LLMの出力する確率分布からかなり離れてしまっています。これを上で挙げたソースコードのようにLLM側で利用する温度と同一のものを利用するように実装し直すと、以下の図のようにほとんどの温度パラメータで効率化できます。特に高い温度においても、Transformers実装ではドラフトモデルなしでの生成よりも遅くなっているのに対し、私の実装では速くなっていることがわかります。 assistは独自実装、assist(hf)はTransformers実装、6.7B(float16)はドラフトモデル無しの速度、125mはドラフトモデルのみで生成した時の速度 トークナイザの互換性 トークナイザ(Tokenizer)は入力文章を言語モデルが扱えるようにトークンIDの列に変換するモジュールであり、モデルによってしばしば異なるトークナイザが使われています。 しかしAssisted Generationを使うにあたり、LLMとドラフトモデルのトークナイザは同じものを使う必要があります。これは内部ではトークンIDを用いてモデル間をやり取りしており、IDが共通であればトークン列をそのまま渡せて効率が良いためです。これに対し、軽量版モデルが存在しないなどでどうしても異なるトークナイザを使いたい場合は2つのトークナイザでトークン列を相互に変換することでAssisted Generationを正しく動かすことができます。ただしあまり高速化は期待できないかもしれません。以下の例ではllm-jp-13Bとopen-calm-small (160Mパラメータ)を用いて生成していますが、生成速度にあまり差が生まれませんでした。 実装のソースコードはこちら import torch import numpy as np import transformers from transformers import AutoModelForCausalLM, AutoTokenizer, GenerationConfig from transformers import TemperatureLogitsWarper, LogitsProcessorList import time class JpCALM (AssistMixin): def __init__ (self, large_tokenizer, assist_tokenizer, large_model, assist_model): self.large_tokenizer = large_tokenizer self.assist_tokenizer = assist_tokenizer self.model = large_model self.assist_model = assist_model def generate (self, text: str , max_len: int ): assist_input_ids = self.assist_tokenizer.encode(text, add_special_tokens= False , return_tensors= "pt" ).to(self.assist_model.device) large_input_ids = self.large_tokenizer.encode(text, add_special_tokens= False , return_tensors= "pt" ).to(self.model.device) self.cache = {} self.cache[ "matches" ] = [] self.logits_processor = LogitsProcessorList() self.logits_warper = LogitsProcessorList() while True : # 5つ先読み assist_draft_ids = self.draft(self.assist_tokenizer.eos_token_id, assist_input_ids, max_assistant_tokens= 5 , do_sample= False ) # 先読みしたassistantのtokenを変換 candidate_words = self.assist_tokenizer.decode(assist_draft_ids[ 0 , assist_input_ids.shape[ 1 ]:], skip_special_tokens= True ) large_candidate_ids = self.large_tokenizer.encode(candidate_words, add_special_tokens= False , return_tensors= "pt" ).to(self.model.device)[:, 1 :] large_draft_ids = torch.cat((large_input_ids, large_candidate_ids), dim= 1 ) # 検証 large_next_input_ids = self.verify(self.large_tokenizer.eos_token_id, large_input_ids, large_draft_ids, max_len, do_sample= False ) # attentionのキャッシュを整理 self.crop_cache(assist_input_ids, large_next_input_ids) # 検証したLLMのtokenを変換 selected_tokens = large_next_input_ids[:, large_input_ids.shape[ 1 ]:] large_input_ids = large_next_input_ids large_input_words = self.large_tokenizer.decode(large_input_ids[ 0 ], skip_special_tokens= True ) valid_words = self.large_tokenizer.decode(selected_tokens[ 0 ], skip_special_tokens= True ) assist_valid_ids = self.assist_tokenizer.encode(valid_words, add_special_tokens= False , return_tensors= "pt" ).to(self.assist_model.device) assist_input_ids = torch.cat((assist_input_ids, assist_valid_ids.long()), dim=- 1 ) if large_input_ids.shape[ 1 ] >= max_len or large_input_ids[ 0 , - 1 ] == self.large_tokenizer.eos_token_id: break return large_input_words large_tokenizer = AutoTokenizer.from_pretrained( "llm-jp/llm-jp-13b-v1.0" ) assist_tokenizer = AutoTokenizer.from_pretrained( "cyberagent/open-calm-small" ) large_model = AutoModelForCausalLM.from_pretrained( "llm-jp/llm-jp-13b-v1.0" , device_map= "auto" , torch_dtype=torch.float16, load_in_8bit= True ) assist_model = AutoModelForCausalLM.from_pretrained( "cyberagent/open-calm-small" , device_map= "auto" , torch_dtype=torch.float16) assisted_jp = JpCALM(large_tokenizer, assist_tokenizer, large_model, assist_model) print ( "=========assisted===========" ) tic = time.perf_counter() with torch.no_grad(): out1 = assisted_jp.generate( "自然言語処理とは何か" , 128 ) tac = time.perf_counter() print (out1) print (tac - tic, "sec" ) matches = np.array(assisted_jp.cache[ "matches" ]) print ( "generated tokens per cycle:" , matches.mean() + 1 ) print ( "acceptance rate:" , (matches > 0 ).sum() / len (matches)) print ( "=========baseline===========" ) tic = time.perf_counter() with torch.no_grad(): tokenized_input = assisted_jp.large_tokenizer.encode( "自然言語処理とは何か" , add_special_tokens= False , return_tensors= "pt" ).to(assisted_jp.model.device) output = assisted_jp.model.generate( tokenized_input, max_length= 128 , )[ 0 ] out2 = assisted_jp.large_tokenizer.decode(output) tac = time.perf_counter() print (out2) print (tac - tic, "sec" ) assert out1 == out2 """ =========assisted=========== 自然言語処理とは何か (岩波新書) | 西垣 通 |本 | 通販 | AmazonKindleストアでは、 自然言語処理とは何か (岩波新書)を、Kindle無料アプリまたはKindle電子書籍リーダーで今すぐお読みいただけます。Kindle電子書籍リーダーの 詳細はこちら自然言語処理とは何か (岩波新書) がカートに入りました自然言語処理とは何か (岩波新書) 新書 – 2016/11/21¥ 886 ¥ 13.10530449775979 sec generated tokens per cycle: 1.7910447761194028 acceptance rate: 0.23880597014925373 =========baseline=========== 自然言語処理とは何か (岩波新書) | 西垣 通 |本 | 通販 | AmazonKindleストアでは、 自然言語処理とは何か (岩波新書)を、Kindle無料アプリまたはKindle電子書籍リーダーで今すぐお読みいただけます。Kindle電子書籍リーダーの 詳細はこちら自然言語処理とは何か (岩波新書) がカートに入りました自然言語処理とは何か (岩波新書) 新書 – 2016/11/21¥ 886 ¥ 14.313117458950728 sec """ 更なる高速化: Token Tree Verification Assisted Generationの速度は先読みがどれだけ当たるかにかかっています。しかし連続して当てられる確率は指数関数的に減少していくため、ドラフトモデルがLLMの出力分布をうまく再現しかなりの精度で先読みを当てないと生成速度が伸び悩んでしまいます。例えば前節で挙げたllm-jp-13Bとopen-calm-smallの組み合わせでは平均で1.79個しか生成できていませんでした。これに対し、ドラフトモデルを変えずに先読みの正解率を向上させる工夫としてToken Tree Verification 4 という手法を活用できます。 LLMの文章生成メカニズム② Causal Language ModelにおけるAttention Maskについて 文章生成に使われているCausal Language Modelでは後ろの入力トークンが前の予測トークンに影響を与えないという話がありました。これはTransformerモデル内でトークン同士が影響し合うSelf-Attention部分にマスクを施すことで実現しています。 このマスクを改造することでより複雑な制約を課すことができます。 例えば "The quick brown fox jumps over" と "The quick brown fox runs around" を同時にLLMに入力したい場合、 "The quick brown fox jumps over runs around" と1行にまとめたのち、"jumps"と"runs", "over"と"around"などといったトークン間のattentionをマスクし取り除くことで、それぞれの文章を独立に推論した時と同じ結果を得ることができます。この手法は、前方で一致しているトークン列が長ければ長いほど、複数文をバッチでまとめるよりメモリ量や計算量的な面で有利になります。 これを一般化すると、分岐した先読みトークン木を1回のLLM推論で検証することができます。 元論文 では複数のドラフトモデルからそれぞれ先読みを生成し、それらを1つのトークン木に集約していましたが、今回は1つのドラフトモデルから複数の先読みを生成してみます。 Token Tree Verificationを用いたgenerate() facebook/opt モデルの例に戻って、先読みがどれくらい当たっているのかを可視化してみます。6.7B, 13Bに対して125Mのモデルをドラフトモデルとして利用し、LLMが出力したトークンがドラフトモデルにおいて何番目までの候補に入っていたかを以下の図に示しました。 これまでの手法ではドラフトモデルが採用するのは一番確率の高いトークン、すなわちtop-1のトークンなので約70%の割合で先読みに成功していることがわかります。さらにtop-2まで採用したとすると正解率は80%に上昇します。先読みに複数選択肢を用意することには効果がありそうです。 ではどのように選択肢を用意すれば良いでしょうか。あまり当たらなそうな先読みトークンに対しては、そのさらに先を読むことによる利益は少なそうです。そこでドラフトモデルの出力確率をそのまま先読みが当たる確率と見做してしまい、先読みが当たる数の期待値が最大になるように木を生成します。 実装のソースコードはこちら from dataclasses import dataclass, field import heapq from typing import Dict, List import torch import torch.nn.functional as F from transformers.generation.logits_process import LogitsProcessorList from transformers.generation.stopping_criteria import StoppingCriteriaList @ dataclass (order= True ) class Node : nll: float # 確率のnegative logが小さい順で幅優先探索を行う gain: int tokens: torch.LongTensor = field(compare= False ) attention_mask: torch.LongTensor = field(compare= False ) all_draft_tokens: torch.LongTensor = field(compare= False ) class AssistTreeMixin : def draft ( self, eos_token_id: int , input_ids: torch.LongTensor, max_assistant_tokens: int , ) -> torch.LongTensor: self.cache[ "assistant_prob_list" ] = [] seq_len = input_ids.shape[ 1 ] max_len = seq_len + max_assistant_tokens pq = [] device = self.assist_model.device heapq.heappush(pq, Node( 0 , 0 , torch.LongTensor([]).to(device), torch.ones_like(input_ids), torch.LongTensor([]).to(device))) draft_ids = input_ids draft_masks = [] draft_nodes = [] for idx in range (max_assistant_tokens+ 1 ): top = heapq.heappop(pq) if len (top.tokens) > 0 : draft_ids = torch.cat((draft_ids, top.tokens[:, None ]), dim=- 1 ) draft_len = draft_ids.shape[ 1 ] attention_mask = F.pad(top.attention_mask, ( 0 , draft_len - top.attention_mask.shape[ 1 ])) attention_mask[:, - 1 ] = 1 if idx > 0 : # トークン木に採用 draft_masks.append(F.pad(attention_mask, ( 0 , max_len - attention_mask.shape[ 1 ]))) draft_nodes.append(top) if "assistant_past_key_values" in self.cache: prev_seq_len = self.cache[ "assistant_past_key_values" ][ 0 ][ 0 ].shape[- 2 ] new_token_len = draft_ids.shape[ 1 ] - prev_seq_len assist_inputs = draft_ids[:, -new_token_len:] assistant_model_outputs = self.assist_model( assist_inputs, attention_mask=attention_mask, past_key_values=self.cache[ "assistant_past_key_values" ]) else : assistant_model_outputs = self.assist_model(draft_ids, attention_mask=attention_mask) self.cache[ "assistant_past_key_values" ] = assistant_model_outputs.past_key_values assist_new_logits = assistant_model_outputs.logits[:, - 1 , :] # (batch, vocab) assist_new_logits = self.logits_processor(draft_ids, assist_new_logits) assist_new_logits = self.logits_warper(draft_ids, assist_new_logits) assist_new_logprobs = F.log_softmax(assist_new_logits, dim=- 1 ) # (batch, vocab) # 計算量節約のために探索数を5つに制限 assist_new_topk = torch.topk(assist_new_logprobs, k= 5 , dim=- 1 ) # (batch, k) for k in range ( 5 ): new_token = assist_new_topk.indices[:, k] new_nll = -assist_new_topk.values[ 0 , k] if new_token != eos_token_id: heapq.heappush(pq, Node(top.nll + new_nll, top.gain+ 1 , new_token, attention_mask, torch.cat((top.all_draft_tokens, new_token[:])))) return draft_ids, torch.cat(draft_masks, dim= 0 ), draft_nodes def verify ( self, eos_token_id: int , input_ids: torch.LongTensor, candidate_input_ids: torch.LongTensor, draft_masks: torch.LongTensor, draft_nodes: List[Node], max_len: int , ) -> torch.LongTensor: candidate_length = candidate_input_ids.shape[ 1 ] - input_ids.shape[ 1 ] tgt_len = candidate_input_ids.shape[ 1 ] def make_tree_mask (_attention_mask, _input_shape, inputs_embeds, past_key_values_length): # Causal Maskを上書きする tree_mask = torch.tril(torch.ones(tgt_len, tgt_len)) tree_mask[-candidate_length:, -candidate_length:] = draft_masks[:, input_ids.shape[ 1 ]:] tree_mask = torch.full((tgt_len, tgt_len), torch.finfo(inputs_embeds.dtype).min).masked_fill(tree_mask > 0 , 0 ) if past_key_values_length > 0 : tree_mask = torch.cat((torch.zeros(tgt_len-past_key_values_length, past_key_values_length), tree_mask[past_key_values_length:, past_key_values_length:]), dim=- 1 ) return tree_mask[ None , None ].to(inputs_embeds.dtype).to(inputs_embeds.device) self.model.model.decoder._prepare_decoder_attention_mask = make_tree_mask cur_len = input_ids.shape[ 1 ] if "past_key_values" in self.cache: prev_seq_len = self.cache[ "past_key_values" ][ 0 ][ 0 ].shape[- 2 ] new_token_len = candidate_input_ids.shape[ 1 ] - prev_seq_len model_attn = torch.ones_like(candidate_input_ids) model_input_ids = candidate_input_ids[:, -new_token_len:] outputs = self.model( model_input_ids, attention_mask=model_attn, past_key_values=self.cache[ "past_key_values" ], ) else : outputs = self.model(candidate_input_ids) logits = outputs.logits for i in range (candidate_length): logits[:, i, :] = self.logits_processor(candidate_input_ids[:, :cur_len + i], logits[:, i, :]) for i in range (candidate_length): logits[:, i, :] = self.logits_warper(candidate_input_ids[:, :cur_len + i], logits[:, i, :]) speculative_ids = candidate_input_ids[:, -candidate_length:] selected_tokens = logits[:, -candidate_length- 1 :, :].argmax(- 1 ) best_sele = torch.LongTensor([]).to(input_ids.device) best_draft_mask = torch.cat((torch.ones(input_ids.shape[ 1 ]), torch.zeros(candidate_length))) n_matches = - 1 longest_tokens = 0 for i, node in enumerate (draft_nodes): selected_tokens_i = torch.cat((selected_tokens[ 0 , 0 : 1 ], selected_tokens[ 0 , 1 :][draft_masks[i,-candidate_length:]> 0 ])) streak = (~(node.all_draft_tokens == selected_tokens_i[:- 1 ])).cumsum( 0 ) < 1 n_matches_i = streak.sum().cpu().item() longest_tokens = max (longest_tokens, len (node.all_draft_tokens)) if n_matches_i > n_matches: n_matches = n_matches_i best_sele = selected_tokens_i best_draft_mask = draft_masks[i] self.cache[ "best" ] = longest_tokens == n_matches self.cache[ "mask_to_cache" ] = best_draft_mask > 0 n_matches = min (max_len - cur_len, n_matches) self.cache[ "matches" ].append(n_matches) self.cache[ "past_key_values" ] = outputs.past_key_values verified = torch.cat((input_ids, best_sele[ None , :n_matches]), dim=- 1 ) if verified[ 0 , - 1 ] == eos_token_id or verified.shape[ 1 ] == max_len: return verified # add one more token verified = torch.cat((verified, best_sele[ None , n_matches:n_matches+ 1 ]), dim=- 1 ) return verified def crop_cache (self, input_ids): # Discard past key values relative to unused assistant tokens mask = self.cache[ "mask_to_cache" ] length = input_ids.shape[ 1 ] - 2 self.cache[ "past_key_values" ] = tuple ([( kv[ 0 ][:, :, mask, :][:,:,:length,:], kv[ 1 ][:, :, mask, :][:,:,:length,:], ) for kv in self.cache[ "past_key_values" ]]) self.cache[ "assistant_past_key_values" ] = tuple ([( kv[ 0 ][:, :, mask, :][:,:,:length,:], kv[ 1 ][:, :, mask, :][:,:,:length,:], ) for kv in self.cache[ "assistant_past_key_values" ]]) class SpecDecoder (AssistTreeMixin): def __init__ (self, tokenizer, large_model, assist_model): self.tokenizer = tokenizer self.model = large_model self.assist_model = assist_model @torch.no_grad() def generate (self, input_ids: torch.LongTensor, max_new_len: int , only_draft= False ): max_len = input_ids.shape[ 1 ] + max_new_len self.cache = {} self.cache[ "matches" ] = [] self.cache[ "first_assistant_prob" ] = [] self.cache[ "verified" ] = [] self.max_assistant_tokens = 5 while True : draft_ids, draft_masks, draft_nodes = self.draft(self.tokenizer.eos_token_id, input_ids, max_assistant_tokens=self.max_assistant_tokens) self.cache[ "draft" ] = (draft_ids, draft_masks, draft_nodes) if only_draft: break new_input_ids = self.verify(self.tokenizer.eos_token_id, input_ids, draft_ids, draft_masks, draft_nodes, max_len=max_len) n_matches = new_input_ids.shape[ 1 ] - input_ids.shape[ 1 ] - 1 if self.cache[ "best" ]: self.max_assistant_tokens += 2 else : self.max_assistant_tokens = max ( 1 , self.max_assistant_tokens - 1 ) self.crop_cache(new_input_ids) input_ids = new_input_ids if input_ids.shape[ 1 ] >= max_len or input_ids[ 0 , - 1 ] == self.tokenizer.eos_token_id: break return input_ids facebook/opt-125m をドラフトモデルとして、"The man worked as a"の続きを5つ先読みすると次のようになりました。 矢印の中身は遷移確率です。"worked as a"の次の単語は自信がないものの、"security"と来たら"guard", "truck"と来たら"driver"というのはそれなりに自信がありそうです。 このように、"security guard"と先読みするだけではこころもとない時に"truck driver"という先読みも含めることができるというのがこのトークン木を活用する利点です。 これを用いてAssisted Generationがどれだけ速くなるか実験しました。 ...残念ながら速くはなりませんでした。トークン木の生成アルゴリズムや先読み数などのチューニングが必要そうです。 元論文の実装 元論文 では複数のドラフトモデルを使って先読みをすることを想定しており、複数のGPUにドラフトモデルを分散させるなどの効率化手法についても触れています。それに加えて、この章ではGreedy Decodingにしか触れませんでしたが、サンプリングを行った際の速度についても実験されています。また、公式実装は FlexFlow で利用できます。 おわりに 本記事では、Assisted GenerationというLLMの推論高速化手法についてご紹介しました。 NTT Com では、大規模言語モデルおよび生成AIを活用したプロダクト・ソリューション開発、社内DXに挑戦するソフトウェアエンジニアを募集しています!詳細は以下のリンクをご覧ください。 hrmos.co イノベーションセンターでは、本人の意志に応じて複数のプロジェクトへ参画できる兼務制度が用意されています。詳しくは イノベーションセンター テクノロジー部門 紹介デッキ をご覧ください。 ↩ https://huggingface.co/blog/assisted-generation ↩ https://arxiv.org/abs/2302.01318 ↩ https://arxiv.org/abs/2305.09781v2 ↩
アバター
サマリ 概要 検証 1. SR OS での PCC 設定と PCEP セッション確立 2. SR OS での LSP テンプレートの作成 3. Pola PCE でのパス設定 4. SR OS でパス確認と疎通確認 まとめ サマリ PCEP を用いた 外部 PCE から SR OS へのパス設定に成功 この記事は Multi-AS Segment Routing 検証連載の第 19 回です。過去の記事一覧は こちら にあります。 概要 イノベーションセンターの田島です。本連載のような Segment Routing 関連の技術検証や、自動化による高度な運用を開発しています。 本記事では SR OS を Path Computation Client (PCC) として設定し、転送経路を示すパス (Label Switched Path; LSP) を外部から設定できるか検証します。 パス設定には標準プロトコルである Path Computation Element Communication Protocol (PCEP) を用いて、複数種類の Path Computation Element (PCE) が利用可能なことを確かめます。 ルーター外部からパスを設定する目的や、 PCEP に関する PCE や PCC の動作概要などの前提知識は、過去の 第 10 回の記事 に詳細がありますのでご参照ください。 過去に複数の PCE を検証した 第 11 回の記事 と同様に L3VPN の経路制御を検証します。 ただし、SR OS では Color を指定したパスの設定ができないため、 本記事では Color ごとのパス選択ではなく PE 単位でのパス設定を行います。 2023 年 11 月現在の最新 SR OS である 23.10 でも Color 指定は未対応のようです。 検証 本記事での検証は PE 間の転送経路を PCE から設定し、VPN 通信がその経路に従って転送されることを確認します。 PCE は IOS XR SR-PCE 等も利用可能ですが、今回は我々で開発している Pola PCE を使用します。なお詳細は割愛しますが IOS XR SR-PCE でも本記事と同等の機能検証が確認されました。 次の図のような検証トポロジーで実施します。 用いたコンポーネントのバージョンは下記の通りです。 SR OS: 22.7.R1 Pola PCE: 1.2.1 L3VPN の設定は 第 12 回の記事 で既出のため、設定済みとします。 Pola PCE の導入や起動に関しては Pola PCE のレポジトリ中の Getting Started with Pola PCE が参考になります。 検証は次の手順で進めます。 SR OS での PCC 設定と PCEP セッション確立 SR OS での LSP テンプレートの作成 Pola PCE でのパス設定 SR OS でパス確認と疎通確認 1. SR OS での PCC 設定と PCEP セッション確立 rt01 (SR OS) と Pola PCE の間で PCEP セッションを確立させます。 Pola PCE は下記の設定で起動済みとします。 user@pola:~$ cat /etc/polad/polad.yaml global: pcep: address: "10.0.255.253" port: 4189 grpc-server: address: "127.0.0.1" port: 50052 log: path: "/var/log/pola/" name: "polad.log" ted: enable: false rt01 では下記の設定を入れ、 PCE とのセッションを有効にします。 [ro:/configure] A:admin@rt01# /info router pcep pcc { admin-state enable local-address 10.255.0.1 peer 10.0.255.253 { admin-state enable } } PCEP セッションが確立したことを確認します。 Pola PCE 側 user@pola:~$ pola session --port=50052 sessionAddr(0): 10.255.0.1 rt01 (SR OS) 側 [ro:/configure] A:admin@rt01# /show router pcep pcc detail =============================================================================== Path Computation Element Protocol (PCEP) Path Computation Client (PCC) Info =============================================================================== Admin Status : Up Oper Status : Up Unknown Msg Limit : 10 msg/min Keepalive Interval : 30 seconds DeadTimer Interval : 120 seconds Capabilities List : stateful-delegate stateful-pce segment-rt-path rsvp- path pce-initiated-lsp p2mp p2mp-delegate p2mp- initiate association multipath Address : 10.255.0.1 Address Ipv6 : (Unspecified) Report Path Constraints: True Redelegation Interval : 90 seconds State Interval : 180 seconds State Timer Action : remove Max SR-TE PCE Init Lsps: 8191 Open Wait Timer : 60 seconds Keep Wait Timer : 60 seconds Sync Timer : 60 seconds Request Timer : 120 seconds Connection Timer : 60 seconds Allow Negotiations : False Max Sessions : 1 Max Unknown Req : 1000 =============================================================================== これで Pola PCE と rt01 の間でパスの情報を送受信できるようになりました。 2. SR OS での LSP テンプレートの作成 SR OS では PCEP 経由で受け取った LSP を SR-TE の LSP として扱います。 機能の有効化には pce-init-lsp sr-te フラグを設定します。 その上で PCEP にて受け取った LSP をインスタンス化するために lsp-template を用意します。 設定をまとめると次の通りです。 [gl:/configure] A:admin@rt01# /info router mpls admin-state enable path "pce-init" { admin-state enable } lsp-template "pce-init-template" { admin-state enable type p2p-sr-te-pce-init default-path "pce-init" pce-report true template-id default max-sr-labels { additional-frr-labels 2 } } pce-init-lsp { sr-te { admin-state enable } } これでパス設定の準備ができました。 3. Pola PCE でのパス設定 Pola PCE でパス定義を読み込み、 rt01 へ送信します。 パスの定義は下記の通りです。 user@pola:~$ cat policy.yaml srPolicy: name: PE1-PE3 pcepSessionAddr: 10.255.0.1 srcAddr: 10.255.0.1 dstAddr: 10.255.0.3 color: 100 segmentList: - sid: 16002 nai: 10.255.0.2 - sid: 16003 nai: 10.255.0.3 pola sr-policy add コマンドを利用し policy を追加することで上記のパスが送信されます。 user@pola:~$ pola sr-policy add -f policy.yaml --no-link-state --port 50052 success! Pola PCE 側で設定済みのパスがあることを確認できます。 こちらは PCC 側が受領した応答なので、 PCC 側で反映された情報が表示されます。 ここで前述の通り SR OS で Color は無視され 0 となっていることが確認できます。 user@pola:~$ pola sr-policy list --port 50052 Session: 10.255.0.1 PolicyName: PE1-PE3 SrcAddr: 10.255.0.1 DstAddr: 10.255.0.3 Color: 0 Preference: 0 SegmentList: 16002 -> 16003 4. SR OS でパス確認と疎通確認 rt01 では Pola PCE から受信したパスの情報を確認し、それが使用されているか確かめます。 まず SR-TE の LSP として途中経路とともに登録されているかを確認します。 送信元ルーターと、送信先ルーター、途中の経路が確認できます。 [gl:/configure router "Base" mpls] A:admin@rt01# /show router mpls sr-te-lsp =============================================================================== MPLS SR-TE LSPs (Originating) =============================================================================== LSP Name Tun Protect Adm Opr To Id Path ------------------------------------------------------------------------------- PE1-PE3 16390 N/A Up Up 10.255.0.3 ------------------------------------------------------------------------------- LSPs : 1 =============================================================================== [ro:/configure] A:admin@rt01# /show router mpls sr-te-lsp path "PE1-PE3" detail =============================================================================== MPLS SR-TE LSP PE1-PE3 Path (Detail) =============================================================================== Legend : S - Strict L - Loose A-SID - Adjacency SID N-SID - Node SID + - Inherited =============================================================================== ------------------------------------------------------------------------------- LSP SR-TE PE1-PE3 Path pce-init ------------------------------------------------------------------------------- LSP Name : PE1-PE3 Path LSP ID : 55808 From : 10.255.0.1 To : 10.255.0.3 Admin State : Up Oper State : Up Path Name : pce-init Path Type : Primary Path Admin : Up Path Oper : Up Path Up Time : 0d 00:52:54 Path Down Time : 0d 00:00:00 Retry Limit : 0 Retry Timer : 30 sec Retry Attempt : 0 Next Retry In : 0 sec PathCompMethod : pce OperPathCompMethod: pce MetricType : N/A Oper MetricType : N/A LocalSrProt : preferred Oper LocalSrProt : preferred LabelStackRed : Disabled Oper LabelStackRed: Disabled Bandwidth : No Reservation Oper Bandwidth : 0 Mbps Hop Limit : 255 Oper HopLimit : 255 Setup Priority : 0 Oper SetupPriority: 0 Hold Priority : 0 Oper HoldPriority : 0 Inter-area : N/A PCE Updt ID : 1 PCE Updt State : Success PCE Upd Fail Code: noError PCE Report : Enabled Oper PCE Report : Enabled PCE Control : Enabled Oper PCE Control : Enabled Include Groups : Oper IncludeGroups: None None Exclude Groups : Oper ExcludeGroups: None None Last Resignal : n/a IGP/TE Metric : N/A Oper Metric : 16777215 Oper MTU : 9186 Path Trans : 1 Degraded : False Failure Code : noError Failure Node : n/a Explicit Hops : No Hops Specified Actual Hops : n/a Record Label : 16002 -> n/a Record Label : 16003 BFD Configuration and State Template : None Ping Interval : N/A Enable : False State : notApplicable ReturnPathLabel : None WaitForUpTimer : 4 sec OperWaitForUpTimer: 0 sec WaitForUpTmLeft : 0 StartFail Rsn : N/A =============================================================================== 次にこの SR-TE LSP が SR OS のトンネルとしても反映されていることを確認します。 rt03 である 10.255.0.3 向け経路で有効になっています。 [ro:/configure] A:admin@rt01# /show router fp-tunnel-table 1 =============================================================================== IPv4 Tunnel Table Display Legend: label stack is ordered from bottom-most to top-most B - FRR Backup =============================================================================== Destination Protocol Tunnel-ID Lbl/SID NextHop Intf/Tunnel Lbl/SID (backup) NextHop (backup) ------------------------------------------------------------------------------- 10.0.1.2/32 SR 524290 3 10.0.1.2 1/1/c1/1:0 10.0.2.2/32 SR 524289 3 10.0.2.2 1/1/c2/1:0 10.255.0.2/32 SR-ISIS-0 524291 3 10.0.1.2 1/1/c1/1:0 10.255.0.3/32 SR-ISIS-0 524292 3 10.0.2.2 1/1/c2/1:0 10.255.0.3/32 SR-TE 671751 16003 10.255.0.2 SR ------------------------------------------------------------------------------- Total Entries : 5 ------------------------------------------------------------------------------- =============================================================================== 最後に VPN の経路を確認することで、今回設定した LSP で指定されている経路を通って通信できていることが確認できます。 [ro:/configure] A:admin@rt01# traceroute 192.168.1.254 router-instance "100" traceroute to 192.168.1.254, 30 hops max, 40 byte packets 1 10.0.1.2 (10.0.1.2) 2.59 ms 5.80 ms 2.03 ms 2 10.0.3.1 (10.0.3.1) 3.10 ms 2.78 ms 8.03 ms まとめ 本記事では、SR OS を PCC として使用する場合の PCEP によるパス設定の検証結果を紹介しました。 Color に対応していないため制御できる粒度は大きいですが、各 PCE でのパス設定が可能でした。 (2024/02/29 追記) 新しい記事を公開しました: [Multi-AS Segment Routing 検証連載 #20] Multi-AS の SR-MPLS + VPNv4 環境における AS 間での TE
アバター
はじめに こんにちは、SDPFクラウドでSDN開発を担当している梶浦( @ykajiaaaaa )です。 今回の記事は今夏のインターンシップで私のチームに来ていただいた伊藤さんによるものです。 このインターンシップでは我々が実際に昔出会った問題をベースにトラブルシューティングを行い、その体験記を執筆いただきました。 それではどうぞよろしくお願いします。 目次 はじめに 目次 参加したインターンシップの紹介 配属されたチームについて インターンシップで取り組んだこと 概要 問題の切り分け 現状把握 原因箇所の更なる切り分け 問題の修正 デバッグ方法 ハッシュテーブル エントリの削除 原因の特定 もう1つの問題 トラブルシューティングのまとめ ライブパッチ インターンシップの感想 メンターからのコメント さいごに 参加したインターンシップの紹介 こんにちは、インターンシップ生の 伊藤吉彦 です。普段は研究室でネットワークの構築・運用をしたり、WIDEプロジェクトの vSIX でSDNコントローラを作ったりしています。 2023年8月28日から9月8日の2週間、NTTコミュニケーションズのインターンシップに参加させていただきました。 本インターンシップでは「 エンタープライズ向け大規模クラウドサービスを支えるネットワーク開発 」というテーマで業務に参加しました。本記事ではその体験談を記載します。 配属されたチームについて NTTコミュニケーションズでは、SDPF (Smart Data Platform)というプロダクトの一部であるSDPFクラウドで、IaaSをエンタープライズ向けに提供しています。 このサービスではオンプレのネットワークをそのままクラウドに載せることができ、仮想L2ネットワークの構築やマルチキャストなどに対応しています。 今回、このSDPFクラウドの裏で動作するSDNコントローラのチームでインターンを行いました。 インターンシップで取り組んだこと 概要 SDPFクラウドでは Contrail というSDNコントローラ製品を利用しています。ContrailはJuniper社の製品でありながら、 Tungsten Fabric としてOSS版が公開されています。 今回はそのコードベースを用いて、「サイズが大きいパケットを送信すると、疎通しないことがある」というユーザからの申告をもとに、その原因を突き止めて修正するという業務に取り組みました。 本インターンシップではさらに、以下の情報が新たに与えられ、これらの情報を踏まえて問題を切り分けていき、原因の究明に努めました。 仮想ルータをインストールし直すと復旧するが、10日ほどで同じ症状が起きる 同じ仮想ネットワークに属するVM同士でも同様の症状が見られる 疎通性が全く取れない時もあれば、稀に取れる事もある 問題の切り分け 現状把握 まず、疎通性が取れなくなる状況を分析するために、パケットサイズを変えながら疎通性を確認しました。その結果、パケットサイズが1472Byte以下ではパケットロスは確認できず、 1473Byte 以上になると報告のあった事象が起きていました。 また、SDPFクラウドの転送図は以下のようになっています。ここでSDPFクラウドを構成する各要素について説明します。 HV:HyperVisorの略称で、複数のVMを収容するサーバ vRouter:HV内部で複数VMのトラフィックの仮想化、転送を司るソフトウェアルータ vRouter Data Plane:トラフィックを転送するカーネルモジュール vRouter Agent:vRouterのコントロールプレーン。経路やフロー情報をvRouterに格納する RoutingInstance, FIB, FlowTable:vRouter内部で異なる仮想NW毎に通信を分けるために必要な情報群 tap:仮想NWのインターフェース VM AからVM Bへの疎通が取れないという報告を受けていましたが、分析を続けていく中で、同じHV, Routing Instance以下のVM BからRouterへの疎通においても同様の問題が確認できました。そのため、解析前はネットワークに問題があると考えたのですが、HV内の転送に原因があると分かりました。 原因箇所の更なる切り分け 続いて、原因箇所の更なる切り分けを行いました。その際、以下のツールを使用しました。 tcpdump:パケットキャプチャ contrail-tools dropstats:vRouter上でドロップしたパケットの情報を取得 flow:アクティブなフロー情報を取得 vif:HV上の仮想インターフェースの情報を取得 さらに、 scapy を使って、常に同じパケットを生成し、問題の原因がパケットそのものにあるかも調査しました。結果、IPヘッダのIdentificationフィールド(以降IP ID)を指定すると常に疎通が取れる、あるいは全く疎通が取れないという状況を作り出すことができ、パケットを処理するデータプレーンに原因があると推測されました。この状況でdropstatsコマンドを用いて監視すると、パケットドロップ時にFragment Errorsがカウントアップされました。以上より、vRouterのフラグメント処理に問題があることを突き止められました。 問題の修正 ここまででvRouterに原因があると分かったため、vRouterについて調査しました。 まず初めにvRouter Agentを再起動してみても疎通性は回復しませんでした。そのため、vRouterのカーネルモジュールに原因があると推測されました。vRouterカーネルモジュールは tf-vrouter をモジュール化したものです。 デバッグ方法 カーネルモジュールのデバッグを行う際、カーネルのメモリのステートをdumpしてその内容を読む手法が取られます。意図的にカーネルをクラッシュさせ、コアファイルを生成し、crashコマンドでその中身を読んでいきます。 ハッシュテーブル vRouterではフラグメントをハッシュテーブルで管理しています。ハッシュキーはパケットのソースと宛先アドレス、IP IDなどが使われています。scapyで調査した内容も踏まえ、このハッシュテーブルに問題がありそうだという仮説を立てました。事実、crashコマンドを用いてフラグメントのハッシュテーブルの中身を覗くと、次のような結果が得られました。 crash> struct vr_htable 0xffff914c203ceea0 // fragment hash table struct vr_htable { ht_router = 0xffffffffc0b08620 <router>, ht_hentries = 8192, ht_oentries = 1024, ht_entry_size = 97, ht_key_size = 40, ht_bucket_size = 4, ht_htable = 0xffff914c2b981bc0, ht_otable = 0xffff914c2b981000, ht_dtable = 0xffff914c2b981e00, ht_get_key = 0xffffffffc0ab4e70 <vr_fragment_get_entry_key>, ht_free_oentry_head = 0x0, ht_used_oentries = 1024, ht_used_entries = 8306 } ht_hentries と ht_oentries はハッシュテーブルのエントリ数を、 ht_used_oentries や ht_used_entries はハッシュテーブルのエントリのうちすでに埋まっている数を示しています。 これらの値から、ハッシュテーブルが全て埋まることでハッシュ衝突が起きたために、フラグメントの処理がうまくいかなかったと分かりました。 エントリの削除 vRouterのフラグメントテーブルは定期的にハッシュテーブルのスキャニングが走り、最終更新から一定時間経過しているエントリは解放される仕組みになっています。1回あたり2048エントリ分スキャンされます。どうやらこのスキャンがカーネルをロードした後、1回しか走っていないようでした。 vRouterではスキャンの定期実行のために、linux timerの機能を使っています。まず、functionを実行するtimerを作ります。そして、timerの期限が切れたら、もう一度functionを動かす新たなtimerを作ることで、定期実行を可能にしています。vRouterでは vt_stop_timer に格納された値で条件分岐しており、0であったらtimerが再度作られ、スキャンを再度走らせる仕組みになっています。 原因の特定 以上より、 vt_stop_timer が怪しいと突き止められました。vRouterでvtimerを作っている箇所をコードレビューすると、 kmalloc でメモリ確保をしていました。 kmalloc はメモリを確保するものの初期化しないため、値が不定になってしまいます。ここで vt_stop_timer に0以外の値が入ると、新たなtimerが作られなくなります。 ここまでで、原因の根源はメモリの初期化ミスであることが分かりました。そのため、 zalloc でメモリ確保をするようにコードを修正することで対応しました。実際に修正後のモジュールをリロードすると、問題なく動作する様子が確認できました。 - vtimer = vr_malloc(sizeof(*vtimer), VR_TIMER_OBJECT); + vtimer = vr_zalloc(sizeof(*vtimer), VR_TIMER_OBJECT); if (!vtimer) { vr_module_error(-ENOMEM, __FUNCTION__, __LINE__, sizeof(*vtimer)); goto fail_init; } vtimer->vt_timer = fragment_table_scanner; vtimer->vt_vr_arg = scanner; vtimer->vt_msecs = VR_FRAG_HASH_TABLE_SCANNER_INTERVAL_MSEC; if (vr_create_timer(vtimer)) { vr_module_error(-ENOMEM, __FUNCTION__, __LINE__, 0); goto fail_init; } もう1つの問題 ハッシュエントリが解放されないのはメモリの初期化ミスが原因でした。しかしながら、調査を進めていくうちに、もう1つのバグが起こりうる箇所を特定できました。 エントリが削除対象かを判定する関数でパケットフラグメントの宛先アドレスが 0.0.0.0 であった時、エントリが削除されないようになっていました。これは初期化されていない null を想定したコードですが、nullを表す0と 0.0.0.0 が同一視されることで、削除されなくなっていました。通常、このアドレス宛のパケットは発行されませんが、潜在的に攻撃対象となる可能性がありました。そのため、他の箇所を確認しこの条件がなくても要件を満たせると判断した上で、以下のように修正を加えました。 fe = VR_FRAGMENT_FROM_HENTRY(ent); - if (!fe || ((!fe->f_dip_u) && !(fe->f_dip_l))) + if (!fe) return; vr_get_mono_time(&sec, &nsec); if (sec > fe->f_time + VR_FRAG_HASH_TABLE_TIMEOUT_SECS) { vr_fragment_del(table, fe); } トラブルシューティングのまとめ サイズの大きいパケットが一定確率でドロップしてしまうという問題のトラシューを行いました。問題を切り分けていくうちに、原因はvRouterのデータプレーンにあると突き止めることができ、パケットのフラグメント処理に問題があると分かりました。コアファイルでデバッグをすると、フラグメントのハッシュエントリが解放されておらず、ハッシュのスキャンが走っていないと分かりました。原因の根幹にあったのは、スキャンを定期実行させるためのvtimerのメモリが初期化されていないことでした。 また、攻撃可能性として 0.0.0.0 宛のパケットを投げるとハッシュが埋め尽くされてしまうと分かりました。 以上の2つのバグを発見し、修正を加えることができました。 ライブパッチ 本インターンシップでは、ライブパッチを当てるタスクにも取り組みました。 SDPFクラウドはエンタープライズ向けであるので、パッチを当てる際にサービスの停止時間を無視できません。通常のパッチを当てるやり方ではカーネルモジュールを置き換える必要があり、その間通信ができなくなります。ダウンタイムを極力抑えつつ、修正したパッチを当てるために Linuxカーネルライブパッチ を用いました。この機能を使うことでパッチ適用時に再起動や停止をする必要がなくなります。 以下に示すように、通常のパッチを当てるやり方では通信断が発生しています。 1秒に1回送信しているpingパケットの icmp_seq が5-24まで欠落していることから約19秒の通信断があると分かります。 $ ping 10.0.0.4 PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data. 64 bytes from 10.0.0.4: icmp_seq=1 ttl=64 time=1.24 ms 64 bytes from 10.0.0.4: icmp_seq=2 ttl=64 time=0.605 ms 64 bytes from 10.0.0.4: icmp_seq=3 ttl=64 time=0.766 ms 64 bytes from 10.0.0.4: icmp_seq=4 ttl=64 time=0.664 ms 64 bytes from 10.0.0.4: icmp_seq=5 ttl=64 time=0.771 ms # ここから 64 bytes from 10.0.0.4: icmp_seq=24 ttl=64 time=395 ms # ここまで 64 bytes from 10.0.0.4: icmp_seq=25 ttl=64 time=0.629 ms 64 bytes from 10.0.0.4: icmp_seq=26 ttl=64 time=0.700 ms 64 bytes from 10.0.0.4: icmp_seq=27 ttl=64 time=0.643 ms 64 bytes from 10.0.0.4: icmp_seq=28 ttl=64 time=0.626 ms 64 bytes from 10.0.0.4: icmp_seq=29 ttl=64 time=0.615 ms ^C --- 10.0.0.4 ping statistics --- 29 packets transmitted, 11 received, 62% packet loss, time 28606ms rtt min/avg/max/mdev = 0.605/36.612/395.474/113.482 ms 今回、 kpatch を用いてライブパッチを実装しました。 0.0.0.0 の方はライブパッチを当てると即座にバグが修正されました。しかし、vtimerの方はタイマー作成時に mod_timer で繰り返し実行を実装している仕組み上、単にvtimerのメモリ初期化をするライブパッチを当てても修正されません。そこで、 contrail-tools のコマンドを実行するとvtimerが新たに作られるように修正を加えました。通常のパッチを当てるやり方では、通信断が回復するまで約19秒かかっていましたが、ライブパッチを使うことでダウンタイムを大幅に削減できました。結果は以下に示す通りで、ダウンタイムはほとんどありません。 PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data. 64 bytes from 10.0.0.4: icmp_seq=1 ttl=64 time=1.94 ms 64 bytes from 10.0.0.4: icmp_seq=2 ttl=64 time=0.388 ms 64 bytes from 10.0.0.4: icmp_seq=3 ttl=64 time=0.559 ms 64 bytes from 10.0.0.4: icmp_seq=4 ttl=64 time=0.520 ms 64 bytes from 10.0.0.4: icmp_seq=5 ttl=64 time=0.471 ms 64 bytes from 10.0.0.4: icmp_seq=6 ttl=64 time=0.373 ms 64 bytes from 10.0.0.4: icmp_seq=7 ttl=64 time=0.437 ms 64 bytes from 10.0.0.4: icmp_seq=8 ttl=64 time=0.440 ms 64 bytes from 10.0.0.4: icmp_seq=9 ttl=64 time=0.496 ms 64 bytes from 10.0.0.4: icmp_seq=10 ttl=64 time=0.351 ms 64 bytes from 10.0.0.4: icmp_seq=11 ttl=64 time=0.522 ms 64 bytes from 10.0.0.4: icmp_seq=12 ttl=64 time=0.551 ms 64 bytes from 10.0.0.4: icmp_seq=13 ttl=64 time=0.448 ms 64 bytes from 10.0.0.4: icmp_seq=14 ttl=64 time=0.463 ms 64 bytes from 10.0.0.4: icmp_seq=15 ttl=64 time=0.532 ms 64 bytes from 10.0.0.4: icmp_seq=16 ttl=64 time=0.501 ms 64 bytes from 10.0.0.4: icmp_seq=17 ttl=64 time=0.530 ms 64 bytes from 10.0.0.4: icmp_seq=18 ttl=64 time=0.432 ms # ここから 64 bytes from 10.0.0.4: icmp_seq=19 ttl=64 time=0.492 ms # ここまで 64 bytes from 10.0.0.4: icmp_seq=20 ttl=64 time=0.428 ms 64 bytes from 10.0.0.4: icmp_seq=21 ttl=64 time=0.377 ms 64 bytes from 10.0.0.4: icmp_seq=22 ttl=64 time=0.539 ms 64 bytes from 10.0.0.4: icmp_seq=23 ttl=64 time=0.395 ms 64 bytes from 10.0.0.4: icmp_seq=24 ttl=64 time=0.547 ms 64 bytes from 10.0.0.4: icmp_seq=25 ttl=64 time=0.521 ms 64 bytes from 10.0.0.4: icmp_seq=26 ttl=64 time=0.466 ms 64 bytes from 10.0.0.4: icmp_seq=27 ttl=64 time=0.395 ms 64 bytes from 10.0.0.4: icmp_seq=28 ttl=64 time=0.536 ms 64 bytes from 10.0.0.4: icmp_seq=29 ttl=64 time=0.332 ms 64 bytes from 10.0.0.4: icmp_seq=30 ttl=64 time=0.495 ms ^C --- 10.0.0.4 ping statistics --- 30 packets transmitted, 30 received, 0% packet loss, time 29639ms rtt min/avg/max/mdev = 0.332/0.516/1.949/0.273 ms インターンシップの感想 本インターンシップでは、普段あまり触る機会のないlinuxカーネルモジュールやライブパッチを体験でき、非常に楽しかったです。 メンターの梶浦さんを始めとし、チームの皆さんには色々と補助をしてもらいました。チームは普段リモート業務が主流のところをわざわざ対面で実施してくださり、本当に感謝しています。2週間という短い期間でしたが、おかげさまで大変充実した時間を過ごすことができました。 また、データセンターの見学や送別会で、インターネット作ってきて今はIOWNに携わっている日本電信電話株式会社所属の先輩とお話をする機会を設けていただいたりと、貴重な体験をさせたいただきました。 最後になりますが、本インターンシップでさまざまな経験をさせていただき本当にありがとうございます。 メンターからのコメント 梶浦です。伊藤さん、まずはお疲れ様でした。 2週間という短い期間かつ、普段あまり触れない領域であったにも関わらず持ち前の深い洞察力ですぐに深い部分まで理解しておられました。 最後には我々も初挑戦だったカーネルライブパッチまで実装いただき、実務上も大変助かりました。ありがとうございます。 この類まれな技術力と課題解決能力でこれからもご活躍されることを心より信じています。 さいごに NTTコミュニケーションズでは冬期にもインターンシップの開催を予定しており、募集が始まっています。 私のチームでも「 48.エンタープライズ向け大規模クラウドサービスを支えるネットワーク開発 」(「プロダクト・サービスエンジニア」ワークフィールド内にあります)で募集しています。 このポストではSDN開発だけでなく、クラウド上でネットワーク機能を提供するNFV開発や、その裏側にあるバックボーンネットワークの開発にも携わることができます。 このような内容に興味のある方はぜひご応募ください。
アバター
NTTコミュニケーションズ(以下、NTT Com)を含めたドコモグループではこの冬に2種類のインターンシップを開催します! 現場受け入れ型インターンシップ ビジネスグロースワークショップ この記事では NTT Com のリアルな業務を体験できる「現場受け入れ型インターンシップ」について紹介します。 現場受け入れ型インターンシップとは NTTドコモや NTT Com の社員と一緒に働きながら、実務を体験していただくインターンシップです。 セールスやビジネスデザイン、エンジニア、デザイナー、リーガルなど幅広い職種を取り揃えて、業務体験を通じて仕事の理解を深め、成長機会を提供する内容となっています。 今季は2024年2月5日(月)~2月16日(金)の土日祝を除く平日9日間(2 Weeks)で開催されます。開催場所は、出社+リモートワークのハイブリッド形式です(出社割合はポストにより異なります)。 募集ポスト 募集ポストについては下記のページの「受け入れポスト情報」をご覧ください。 現場受け入れ型インターンシップ 記載されているポストのうち、受け入れ会社に NTTコミュニケーションズ株式会社 と記載されたポストが NTT Com での業務です。 これまでのインターンシップの様子 これまで開催したインターンシップの体験記を NTT Com のエンジニア系ポストに参加した学生の方々が、この NTT Communications Engineers' Blog に寄稿してくれています。 「インターンシップでどんなことに取り組むのだろう?」、「インターンシップを通して何が学べるのだろう?」といった疑問を解消する手助けになれば幸いです。 AI分野 インターンシップでマルチA100 GPUサーバをぶん回してみた - NTT Communications Engineers' Blog セキュリティ分野 セキュリティ技術開発のインターンシップに参加させていただきました!! - NTT Communications Engineers' Blog インターンシップ体験記 〜セキュリティ運用の健全化を目指すMetemcyberの開発〜 - NTT Communications Engineers' Blog インターンシップ体験記 〜Cobalt StrikeのC2サーバ追跡〜 - NTT Communications Engineers' Blog インターンシップ生があるSaaSを用いた未知のC2脅威を実証してみた - NTT Communications Engineers' Blog 攻撃者はいかにしてフィッシングサイトを隠すか?(インターンシップ体験記) - NTT Communications Engineers' Blog ネットワーク分野 ネットワーク知識ゼロの大学院生が、NTTコミュニケーションズのインターンシップに参加してみた - NTT Communications Engineers' Blog インターンシップ体験記 〜BGP-LSの機能をFRRに実装してみた〜 - NTT Communications Engineers' Blog インターンシップ体験記 〜SRv6 L3VPN機能検証〜 - NTT Communications Engineers' Blog インターンシップ体験記 〜SR-MPLS IPv6 Underlay 相互接続検証〜 - NTT Communications Engineers' Blog インターンシップ体験記 〜SRv6 機能を Pola PCE に実装してみた〜 - NTT Communications Engineers' Blog ソフトウェア/クラウド分野 IoT Connect Gatewayを使ってみた 番外編 ~インターンシップでリリース前の機能を使って開発してみた~ - NTT Communications Engineers' Blog IoT Connect Gatewayを使ってみた 番外編 第2回 ~インターンシップでStorage転送機能を使って開発してみた~ - NTT Communications Engineers' Blog テレプレゼンスPJ インターン参加レポート - NTT Communications Engineers' Blog インターンシップ体験記 〜SDNコントローラの性能改善〜 - NTT Communications Engineers' Blog インターン参加記 ~GPUクラスタ管理者への道~ - NTT Communications Engineers' Blog TypeScript未経験の学生がSkyWayの開発に取り組んでみた(インターンシップ体験記) - NTT Communications Engineers' Blog データプレーンに起きたバグにパッチを当ててみた(インターンシップ体験記) - NTT Communications Engineers' Blog なお、NTT Com のデザイン系ポスト(KOEL)については KOEL公式note 、NTTドコモのエンジニア系ポストについては ドコモ開発者ブログ をご覧ください。 まとめ みなさんもこの冬、ドコモグループのインターンシップに参加して、興味分野で熱い冬を過ごしてみませんか? 気になるポストの詳細やエントリーはこちらです(再掲)。 現場受け入れ型インターンシップ エントリー締め切りは 2023年11月27日(月)23:59 です。 みなさんのご応募をお待ちしています!
アバター
サマリ SR-MPLS 環境の障害発生時における、通信断を 50ms 以内に抑えた復旧を実現 障害点の隣接ノードでの Fast ReRoute(FRR)設定と、各ノードでの Microloop Avoidance、機器内のタイマ調整を実施 SR OS + IOS XR + Junos の Multi-vendor 環境での動作検証 この記事は Multi-AS Segment Routing 検証連載の第 18 回です。過去の記事一覧は こちら 概要 イノベーションセンターの三島です。 第 9 回 の記事では、IOS XR + Junos の Multi-vendor 構成での SR-MPLS 環境において障害が発生した際に、Fast ReRoute(FRR) を用いて通信断を 50ms 以内に抑える手法についてご紹介しました。 本記事では、SR OS を含めた構成において同様の手法の適用例を紹介するとともに、前回紹介できなかった Microloop Avoidance や トポロジー内のノード間での IGP のタイマー調整といったより詳しい対処ついても紹介します。 トポロジー変更時の通信断抑制と、実現のため対策すべき課題 障害などによって発生するトポロジー変更時の通信断は、あるノードにおいて経路の再計算にかかるまでの間と、それがネットワーク全体で完了するまで間、ネットワーク全体での経路の一貫性が取れていない期間に発生します。 具体的に言い換えると下記2つの課題に分割され、それらを合わせた通信断を 50ms 以内に抑える必要があります。 課題1. 障害発生直後に、障害点と隣接するノードにおいて経路の再計算が行われ、FIB が更新されて転送が再開されるまでの間に発生する通信断 課題2. 障害点の隣接ノードで経路の再計算が行われた後、他ノードの経路計算が完了するまでの間に発生する通信断 課題1. 障害発生直後に、障害点と隣接するノードにおいて経路の再計算が行われ、FIB が更新されて転送が再開されるまでの間に発生する通信断 課題1. については、連載記事 第 9 回 の「障害時の迂回動作について」の図にて紹介しています。 障害が発生した際に、隣接ノードでは障害の検知、IGP の再計算、再計算の結果をハードウェアに反映し転送再開、というステップで復旧に向かいます。 再計算から転送再開には100ミリ秒から秒単位の時間がかかるため、あらかじめバックアップ経路を計算しハードウェアに反映しておき使用するという手法をとります。 これが FRR です。 課題2. 障害点の隣接ノードで経路の再計算が行われた後、他ノードの経路計算が完了するまでの間に発生する通信断 課題2.の課題を図に示します。 IGP の経路計算処理は各ノードで非同期に行われるため、その実行タイミングは揃っていません。 そのため、リンク障害でトポロジーが変化した後では、変化後のトポロジーをもとに転送する経路計算が完了したノードと、変化前のトポロジーをもとに転送する経路計算が未完了なノードの混在した状況が発生します。 このような状況においては、特定のトポロジーにおいて Microloop と呼ばれるルーティングループが発生し通信断を引き起こします。 このタイミングをできるだけ揃える IGP のタイマ調整や、変化後のトポロジーの計算結果の使用を意図的に遅らせる Microloop Avoidance で対処します。 上の図は、障害発生後に rt26 でのみ経路計算が完了し、その他のノードでは未完了と仮定した場合の様子を示しています。 この状況で egress node である rt16 に向かう経路を考えます。 図中の 1. にて障害が発生した後、各ノードで経路計算が開始されます。 その後、2. では rt26 における経路の再計算が終わったため、next-hop が(もしくは outgoing interface が)rt11 に変わっています。 一方、3. の通り、rt11 は経路計算が完了していないため、next-hop が rt26 へのリンクを向いています。 そのため、rt26 は rt11 へとパケットを転送し、r11 は rt26 へとパケットを送ってしまいます。これにより、rt11 で 経路計算が完了するまでの間、4. の通り rt26 と rt11 の間で Microloop が発生します。 Microloop の発生有無は、トポロジーやタイミングによります。また、ホップ数やコストなどのトポロジー条件によっては、障害点とは離れたノードで発生する可能性もあります。 例えば先ほどの例では、コストによっては rt11 と rt23 との間で起こる可能性もありますし、また障害復旧に伴う経路計算においても Microloop は発生する可能性があります。 時系列に沿った各課題の整理 課題1・2を障害発生からの時系列で示すと下記のようになります。 この図は、ある2つのノードにおける障害発生時点からの時系列を示しています。 ノード A は障害発生箇所の隣接ノード、ノード B は障害箇所に直接接していないノードです。 障害が発生した後、隣接ノードであるノード A が障害を検知し、経路計算を開始するとともにトポロジーの変化を IGP により広告します。 また、経路広告を受け取ったノード B でも経路計算が開始されます。各ルーターにおける経路計算は、機器内部の IGP タイマーで定められた間隔毎に実施されます。 これまでに説明した通り、障害が発生した時点から、経路計算が完了するまでの期間は、課題1のループが生じます。 また、あるノードにおける経路計算が完了した時点から、全ノードでの経路計算完了までにかかる時間の期間には課題2. の Microloop が発生する可能性があります。 課題1. の対策としては FRR があります。 FRR は、ルーターがあらかじめ計算したバックアップパスを FIB に保持しておき、障害が発生した際、バックアップパスを利用することで、高速迂回を実現します。 第9回の記事でも紹介した通り、SR においては、TI-LFA と呼ばれる技術により、どのようなトポロジーであってもループフリーにバックアップパスを生成できます。 FRR による保護を、時系列の図に示しました。 図のように、FRR により通信を保護することで、障害発生地点の隣接ノードでの IGP 経路計算が完了するまでの間、バックアップパスを用いることでループを回避できます。 一方、FRR はそのノードの経路計算完了までを保護する技術であるため、経路計算が完了した後、その先のノードで発生する Microloop(課題2)は保護できません。 課題2. の対策としては、Microloop Avoidance と ルーター内の IGP 関連タイマーを調整する手法の2種が存在します。 以下の節にて、それぞれのポイントについて解説します。 Microloop Avoidance Microloop Avoidance は、経路収束後に Microloop が発生する可能性がある場合に、タイマーに従い経路適用を遅延させることで他ノードの経路収束を待ち、Microloop を回避する技術です。 Microloop Avoidance の動作を図に示しました。 前提として、rt26 には各ノードの経路収束が現実的に完了するであろう時間分、Microloop Avoidance の遅延タイマーを設定しておきます。 障害に伴うトポロジー変更が発生した後、rt26 内で IS-IS 等での経路計算が完了した場合を想定します。 ここで、rt26 では Microloop Avoidance により、遅延タイマーの時間分だけ RIB あるいは FIB への経路更新を遅延させ、その代わりに Microloop を回避可能な経路が採用されるよう、新たなラベルを付与し送信します。 タイマーにより経路インストールを遅延させることで、rt11 がパケットを戻してしまうことを抑制できます。 この際、追加のラベルを付与して送信することで、rt23 より先のノードで経路計算が完了した場合でも、rt11 向けにパケットが戻ってくることを防止します。(rt11 より先での Microloop 防止) rt11 での経路計算が完了すると、本来のベストパスでの転送が再開されます。 その後 rt26 で Microloop Avoidance の遅延タイマー分の時間が経過することにより、IS-IS で計算したベストパスを FIB にインストールし、Microloop Avoidance を終了します。 このように、経路収束が現実的に完了するであろう時間まで Microloop Avoidance の遅延を入れることにより、Microloop を回避できます。 Microploop Avoidance による保護を、時系列の図に示しました。 図のように、Microloop Avoidance は、あるノードでの経路計算が完了した後、事前にノード内で設定された時間分の保護を実施し、Microloop を回避します。 ただし、Microloop Avoidance はネットワーク上の全ノードの経路計算完了を検知できる技術ではないため、タイマーで設定された時間が完了するまでの間は保護が行われ続けます。 機器間のタイマー統一 ある機器における経路収束タイミングは、IGP での経路広告に加え、機器内の SPF 実行タイマーや IS-IS による LSP 生成タイマーなど、さまざまな要素が絡み決定されます。 これらのタイマーはベンダー毎に異なるため、経路計算のタイミングに差が生まれ Microloop が発生しやすくなります。 タイマーの設定による保護を、時系列の図に示しました。 図のように、IGP タイマーの値を十分短い時間に統一することにより、機器間の経路計算タイミングの差を削減できます。 これにより、ネットワーク内の全ノードで経路計算が完了するまでの時間を削減でき、課題2の発生時間を短縮可能です。 多くの機器では経路収束に関連するタイマーの値を設定できます。今回対象とするタイマーは下記の2つです。 SPF wait : IS-IS による SPF 計算に対する遅延 LSP wait : IS-IS の LSP 生成に対する遅延 IOS XR と Junos、そして SR OS において SPF wait / LSP wait のデフォルト値は以下の通りです。 SR OS (config name) IOS XR (config name) Junos (config name) SR OS spf-max-wait 5000 ms (spf-interval>maximum-wait) 5000 ms (spf-options holddown) 10000 ms spf-initial-wait 50 ms (spf-interval>initial-wait) 200 ms (spf-options delay) 1000 ms spf-second-wait 200 ms (spf-interval>secondary-wait) 200 ms (spf-options delay) 1000 ms lsp-max-wait 5000 ms (lsp-gen-interval>maximum-wait) 不明 5000 ms lsp-initial-wait 50 ms (lsp-gen-interval>initial-wait) 100 ms (lsp-interval) 10 ms lsp-second-wait 200 ms (lsp-gen-interval>secondary-wait) 100 ms (lsp-interval) 1000 ms 各パラメータの出典は以下の通りです。 IOS XR spf-interval lsp-gen-interval Junos spf-options lsp-interval SR OS spf-wait lsp-wait 特に SPF wait は IOS XR や Junos の標準設定では 100~200 ms 周期で更新されるのに対し、SR OS の標準動作では 1000 ms 周期での更新となっています。 そのため、IOS XR や Junos ルーターにおける経路収束と、SR OS ルーターにおける経路収束には最悪1秒程度の差が存在するため、Microloop の原因となります。 前節の通り、Microloop Avoidance を適切に設定することで、あるノードにおける経路収束後、Microloop Avoidance を防止できます。しかし、Microloop Avoidance 中はパケットに対する不要な追加 Encapsulation が行われるため、一般に長時間の Microloop Avoidance を設定することは好ましくありません。 一方、タイマー統一はあくまでルーター間の経路計算タイミングを近づけるアプローチであり、微小な期間の Microloop は防止できません。 しかし、タイマーを機器間で統一された短い値に設定しておくことで、機器間の経路計算タイミングの差を縮め、Microloop Avoidance の動作時間を短縮可能になります。 ただし、経路計算タイマーの短縮はルーターの負荷を向上させるため、それぞれの環境に合ったパラメータを設定する必要があります。 以降の章では、IOS XR・Junos・SR OS の混在環境において、FRR・Microloop Avoidance・タイマー調整のそれぞれの手法を検証していきます。 検証 本章では、これまでにご紹介したそれぞれの技術を適用し、障害発生時における 50ms 以内の通信断での通信を実現します。 実現にあたっては、概要章で触れた各技術を組み合わせることが必要です。ここでは、下記の順で検証します。 全ての技術を用いない場合の通信断の時間確認 FRR を用いた、障害発生直後の通信断の抑制 FRR と Microloop Avoidance を用いた、断時間 50ms の実現 FRR と タイマー調整による、の抑制手法の紹介 上記の順で検証することで、障害発生後の通信断時間を、各種技術がどのように削減してくかを確かめます。 検証は以下のようなトポロジーを用いて行います。 本検証で用いる各ルーターの機種名と OS のバージョンは以下の通りです。 rt11(NCS55A2: IOS XR 7.5.1) rt16(MX204: Junos 21.3R1.9) rt23(7750SR-1: SR OS 23.3.R1) rt24(7750SR-1: SR OS 23.3.R1) rt26(7750SR-1: SR OS 23.3.R1) 検証の流れ 復旧時間の計測は、 ping コマンドを用いて行います。 VM 間で ICMP パケットを一定間隔(10 ms 毎)で送信し続けておき、 rt16 と rt26 間のリンクを切断した際にどの程度パケットがロスするかを計測する事で切り替えにかかった時間を測定します。 以下の手順で確認します。 vm01 から vm02 に対し、ping コマンドを用いて ICMP パケットを短い一定間隔(0.001秒)で送信し続けておく vm01 では tcpdump コマンドを用いて ICMP の request パケットをキャプチャしておく rt16 と rt26 間リンクの rt26 側インタフェース(xe-0/1/1)をシャットダウンする事で擬似的な障害を発生させる インタフェースをシャットダウンした際に、vm01 から vm02 への ICMP パケットロスがどの程度発生したかを確かめる 事前準備 rt16 と rt26 間で VRF 100 による L3VPN を実装します。 L3VPN の設定は 第 4 回の記事 や 第 12 回の記事 を参考にして以下を実施します。 VRF 100 による L3VPN 作成 BGP color の付与と広告 1.全ての技術を用いない場合の通信断の時間確認 まずは FRR により保護していない場合の復旧時間を計測します。 経路切り替え動作(SR OS) 状態確認 TI-LFA/FRR は未設定のためバックアップパスは作成されていません。 A:user@ar-rt26# tools dump router segment-routing tunnel | no-more =================================================================================================== Legend: (B) - Backup Next-hop for Fast Re-Route (D) - Duplicate label stack is ordered from top-most to bottom-most =================================================================================================== --------------------------------------------------------------------------------------------------+ Prefix | Sid-Type Fwd-Type In-Label Prot-Inst(algoId) | Next Hop(s) Out-Label(s) Interface/Tunnel-ID | --------------------------------------------------------------------------------------------------+ 10.255.2.1 Node Orig/Transit 16009 ISIS-0 10.2.17.1 16009 to_ar-rt11 10.255.2.2 Node Orig/Transit 16010 ISIS-0 10.2.17.1 16010 to_ar-rt11 10.255.2.3 Node Orig/Transit 16011 ISIS-0 10.2.17.1 3 to_ar-rt11 10.255.2.5 Node Orig/Transit 16013 ISIS-0 10.2.17.1 16013 to_ar-rt11 10.255.2.7 Node Orig/Transit 16015 ISIS-0 10.2.17.1 16015 to_ar-rt11 10.255.2.8 Node Orig/Transit 16016 ISIS-0 10.2.15.1 3 to_ar-rt16 10.255.2.23 Node Orig/Transit 16023 ISIS-0 10.2.17.1 16023 to_ar-rt11 10.255.2.24 Node Orig/Transit 16024 ISIS-0 10.2.17.1 16024 to_ar-rt11 10.255.2.25 Node Orig/Transit 16025 ISIS-0 10.2.17.1 16025 to_ar-rt11 10.255.2.26 Node Terminating 16026 IGP-Shared-0 10.2.15.1 Adjacency Transit 524282 ISIS-0 10.2.15.1 3 to_ar-rt16 10.2.17.1 Adjacency Transit 524285 ISIS-0 10.2.17.1 3 to_ar-rt11 --------------------------------------------------------------------------------------------------+ No. of Entries: 12 --------------------------------------------------------------------------------------------------+ vm01 から vm02 へ ICMP request の送信 以下のコマンドを実行します。 user@vm01:~$ sudo ping -i 0.001 192.168.40.1 リンク切断 以下の設定を追加し、rt16 と rt26 間リンクの rt16 側インターフェースである xe-0/1/1 をシャットダウンすることで擬似的に障害を発生させます。 [edit] user@rt16# show | compare [edit interfaces xe-0/1/1] + disable; 復旧に要した時間 vm02 において tcpdump コマンドを用いて、vm01 から受信した ICMP request パケットをキャプチャすると以下のような結果となりました。 rt11・rt23・rt24 を経由する経路へ切り替わると、ホップ数が 3 増加するため ttl は 3 減少します。(62 → 59) ttl に着目し障害が発生した時点でのパケットを探すと ICMP のシーケンス番号 が 4949 から 5061 の間でパケットロスが確認でき、経路が切り替わっている事が分かります。 よって、SR OS では非 FRR での通信復旧に(5061 - 4949) = 112 ms 程度要したことが確認できました。 user@vm02:~$ sudo tcpdump icmp[icmptype] == 8 -i ens192 -v 22:12:02.360488 IP (tos 0x0, ttl 62, id 25319, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 228, seq 4947, length 64 22:12:02.361488 IP (tos 0x0, ttl 62, id 25320, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 228, seq 4948, length 64 22:12:02.362488 IP (tos 0x0, ttl 62, id 25321, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 228, seq 4949, length 64 22:12:02.575251 IP (tos 0x0, ttl 59, id 25443, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 228, seq 5061, length 64 22:12:02.576134 IP (tos 0x0, ttl 59, id 25444, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 228, seq 5062, length 64 22:12:02.577131 IP (tos 0x0, ttl 59, id 25445, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 228, seq 5063, length 64 2. FRR を用いた、障害発生直後の通信断の抑制 続いて、 FRR(TI-LFA)を用いて rt26 の rt16 向けリンクを保護した場合を検証します。 第 9 回 の記事では、IOS XR + Junos の 2 つのベンダー機器を用い、FRR・Topology Independent Loop-Free Alternate(TI-LFA)を用いた高速迂回を実現する方法を紹介しました。 今回は新たに Nokia SR OS(Service Router Operating System)における高速迂回手法と、各社の混在環境での検証を紹介します。 TI-LFA の設定(SR OS) 保護したいノードに対し以下の設定を追加します。 rt26(SR OS) router isis loopfree-alternate ti-lfa node-protect 各経路に対しバックアップパスが計算されている事が確認できます。 rt16(10.255.2.8)に対しては、 16023・16016 の SID を積み増し、to_ar-rt11 のインターフェースから送出するバックアップパスが作成されました。 A:hanabi@ar-rt26# tools dump router segment-routing tunnel | no-more =================================================================================================== Legend: (B) - Backup Next-hop for Fast Re-Route (D) - Duplicate label stack is ordered from top-most to bottom-most =================================================================================================== --------------------------------------------------------------------------------------------------+ Prefix | Sid-Type Fwd-Type In-Label Prot-Inst(algoId) | Next Hop(s) Out-Label(s) Interface/Tunnel-ID | --------------------------------------------------------------------------------------------------+ 10.255.2.1 Node Orig/Transit 16009 ISIS-0 10.2.17.1 16009 to_ar-rt11 (B)10.2.15.1 16024 to_ar-rt16 16009 10.255.2.2 Node Orig/Transit 16010 ISIS-0 10.2.17.1 16010 to_ar-rt11 (B)10.2.15.1 16010 to_ar-rt16 10.255.2.3 Node Orig/Transit 16011 ISIS-0 10.2.17.1 3 to_ar-rt11 (B)10.2.15.1 16024 to_ar-rt16 16011 10.255.2.5 Node Orig/Transit 16013 ISIS-0 10.2.17.1 16013 to_ar-rt11 (B)10.2.15.1 16024 to_ar-rt16 16013 10.255.2.7 Node Orig/Transit 16015 ISIS-0 10.2.17.1 16015 to_ar-rt11 (B)10.2.15.1 16024 to_ar-rt16 16015 10.255.2.8 Node Orig/Transit 16016 ISIS-0 10.2.15.1 3 to_ar-rt16 (B)10.2.17.1 16023 to_ar-rt11 16016 10.255.2.23 Node Orig/Transit 16023 ISIS-0 10.2.17.1 16023 to_ar-rt11 (B)10.2.15.1 16023 to_ar-rt16 10.255.2.24 Node Orig/Transit 16024 ISIS-0 10.2.17.1 16024 to_ar-rt11 (B)10.2.15.1 16024 to_ar-rt16 10.255.2.25 Node Orig/Transit 16025 ISIS-0 10.2.17.1 16025 to_ar-rt11 (B)10.2.15.1 16025 to_ar-rt16 10.255.2.26 Node Terminating 16026 IGP-Shared-0 10.2.15.1 Adjacency Transit 524282 ISIS-0 10.2.15.1 3 to_ar-rt16 (B)10.2.17.1 16023 to_ar-rt11 16016 10.2.17.1 Adjacency Transit 524285 ISIS-0 10.2.17.1 3 to_ar-rt11 (B)10.2.15.1 16024 to_ar-rt16 16011 --------------------------------------------------------------------------------------------------+ No. of Entries: 12 --------------------------------------------------------------------------------------------------+ SR OS ルーターでの経路切り替え動作 vm01 から vm02 への ICMP request の送信 user@vm01:~$ sudo ping -i 0.001 192.168.40.1 リンク切断 [edit] user@rt02# show | compare [edit interfaces xe-0/1/1] + disable; 復旧に要した時間 vm02 において、vm01 から受信した ICMP の request パケット情報を確認します。 ICMP のシーケンス番号が 12637-12633 の パケットが欠けていることから、SR OS では通信復旧に 4 ms 程度要したことが確認できました。 user@vm02:~$ sudo tcpdump icmp[icmptype] == 8 -i ens192 -v 22:20:01.865557 IP (tos 0x0, ttl 62, id 60957, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 229, seq 12631, length 64 22:20:01.866556 IP (tos 0x0, ttl 62, id 60958, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 229, seq 12632, length 64 22:20:01.867560 IP (tos 0x0, ttl 62, id 60959, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 229, seq 12633, length 64 22:20:01.898894 IP (tos 0x0, ttl 59, id 60967, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 229, seq 12637, length 64 22:20:01.899881 IP (tos 0x0, ttl 59, id 60968, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 229, seq 12638, length 64 22:20:01.900860 IP (tos 0x0, ttl 59, id 60969, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.41.1 > vm02: ICMP echo request, id 229, seq 12639, length 64``` また、FRR 実施時は Backup Pathの通りに 16023・16016 の SID が積み増されていることも確認できました。 Nokia の SR-TE から FRR を扱う場合の注意点 SR OS にて lsp や lsp-template と FRR を併用する場合、FRR により追加される SID 数(ラベル数)の最大値を調整する必要があります。 TL-LFA の場合は 2 段の SID が積み増される可能性があるため、下記のように additional-frr-labels を設定します。 max-sr-labels { additional-frr-labels 2 } 3. FRR と Microloop Avoidance を用いた、断時間 50ms の実現 Microloop Avoidance 未導入の場合 前節では、FRR により障害発生直後のパケットロスを防止できました。 しかし、解説の章で触れた通り、あるノードでの計算が完了した後で Microloop によるパケットロスが発生する可能性があります。 ここでは、rt26 での計算が完了した後、rt11 の経路計算が完了するまでの間に Microloop が発生することで、以下のように 95 パケットのロスが生じていることが確認できます。 64 bytes from 192.168.40.1: icmp_seq=10690 ttl=59 time=0.252 ms 64 bytes from 192.168.40.1: icmp_seq=10785 ttl=59 time=0.339 ms Microloop Avoidance の導入 rt16 に以下の設定をし、30秒の間 Microloop Avoidance を適用させます。 user@ar-rt16# set protocols isis spf-options microloop-avoidance post-convergence-path delay 30000 [edit] user@ar-rt16# show | compare [edit protocols isis] + spf-options { + microloop-avoidance { + post-convergence-path { + delay 30000; + } + } + } 設定後、バックアップ経路から再計算後の経路に切り替わったタイミングでのパケットロスは確認できず、正しくMicroloop Avoidance が動作していることが確認できました。 4. (参考)FRR と タイマー調整による、経路計算のタイミング差抑制 rt26 のタイマーを IOS XR のものと同様の値に変更します。 [ro:/configure] A:user@ar-rt26# /info router isis timers spf-wait { spf-max-wait 5000 spf-initial-wait 50 spf-second-wait 200 } lsp-wait { lsp-max-wait 5000 lsp-initial-wait 50 lsp-second-wait 200 } これにより、各タイマーがベンダー間で共通化され、経路収束時間の差異を削減できます。 概要章でも述べた通り、タイマー調整はあくまで機器間の経路計算時間を統一することで、経路計算の完了タイミングを近づけるアプローチです。そのため、このアプローチでは Microloop の発生確率を下げることはできますが、回避はできません。 機器間のタイマーを統一した上で、前節の Microloop Avoidance を適切な時間分設定する上で、不要な encapsulation を最小限にしつつ、 Microloop を回避できます。 検証まとめ それぞれの検証を通じ、IOS XR・Junos・SR OS の混在環境において、FRR と Microloop Avoidance を用いた保護により、障害が発生した際に 50 ms 以下で復旧できることを確認できました。 まとめ 本記事では、SR-MPLS 環境において、50ms 以内で通信を復旧する手法を解説しました。 その中で、障害発生直後に用いる FRR と、その後の機器毎の経路収束タイミングの差異によるループを防止する Microloop Avoidance や各種タイマーの統一手法を紹介し、IOS XR・Junos・SR OS を用いた動作検証を実施しました。 次回の記事では PCEP を用いた SR OS への SR Policy 適用方法について紹介予定です。 (2023/11/13 追記) 公開しました: [Multi-AS Segment Routing 検証連載 #19] SR OS での PCE を用いた LSP Provisioning
アバター
はじめに こんにちは、PS本部C&A部開発オペレーション部門の8G3Tです。AI映像解析ソリューションCOTOHA Takumi Eyesの技術開発や運営保守に取り組んでいます。チームの開発メンバーは6月18日から22日の間に開催されたコンピュータービジョン分野のトップカンファレンスであるCVPR2023にリモートで参加しました。本記事ではCVPR2023に採択された視覚・言語のマルチモーダル技術に関して、私たちが興味深く感じた論文をピックアップしてご紹介します。 なお、今回の学会参加はイノベーションセンターのメディアAIチームと連携して実施しました。NeRF技術(ニューラルネットワークベースの微分可能な3次元レンダリング手法)に関する論文のご紹介や検証結果については、以下のメディアAIチームが取りまとめた記事をぜひご覧ください。 CVPR2023で登場したNeRF論文を紹介 目次 はじめに 目次 視覚言語マルチモーダル技術の概要 画像・映像認識性能の向上に関する論文 Improving Commonsense in Vision-Language Models via Knowledge Graph Riddles RA-CLIP: Retrieval Augmented Contrastive Language-Image Pre-training Fine-tuned CLIP Models are Efficient Video Learners Top-Down Visual Attention from Analysis by Synthesis 新たなタスクを提案した論文 Connecting Vision and Language with Video Localized Narratives Visual Programming: Compositional visual reasoning without training 最後に 視覚言語マルチモーダル技術の概要 人間の学習は本質的に多様なモーダリティを備えており、複数の感覚を合わせて処理することによって新しい情報への理解を深めることが可能となります。近年、コンピュータービジョン分野においてもマルチモーダルのAI技術が急速に発展し、広く注目を集めています。 特定の画像処理タスクのデータセット(例:画像分類)で学習したユニモーダルAIと比較して、画像と自然言語の大規模データで学習したマルチモーダルAIの汎化性能が高く、チャレンジングなフューショットやゼロショットの画像認識タスクにおいて優れた性能を示しました。画像生成のStable Diffusionや質問応答のGPT-4といった汎用性の高いマルチモーダルAIサービスは人間の知的作業全般に変革をもたらしつつあり、マルチモーダルAIの性能向上が求め続けられています。 今回はマルチモーダルAIの画像・映像認識性能を向上させる取り組みに関する最新論文とマルチモーダルAIをベースに新たに提案されたタスクについてご紹介します。 画像・映像認識性能の向上に関する論文 Improving Commonsense in Vision-Language Models via Knowledge Graph Riddles 1 概要 背景: 既存のVLモデルには、人工的な一般知能に向けた重要な要素である常識的知識/推論能力(例えば「レモンは酸っぱい」)が欠けている。 下の画像の例では酸っぱい味がするものに対応する画像として既存のVLモデルがレモンではなくチョコレートケーキを選んでしまっている。 原論文 Figure 1 から引用 この現象の原因の重要な一つとして、既存の大規模なVLデータセットにはあまり常識的な知識が含まれていないことがある。通常のVLデータセット(例えばCOCOやCC 12M)には、名詞や(画像内の実体を直接説明するような)説明形容詞が多く含まれ、動詞や助詞は通常のテキストに比べて少ない。このような分布の違いは、言語のみのモデルとは異なり、VLモデルが純粋にデータセットを拡大することによって、常識的な能力を獲得することは不可能かもしれないことを示唆している。 また、視覚的な質問応答や生成タスクによってコモンセンス能力を評価する既存のベンチマークは、訓練に広く適用できず、データサイズも限られている。これらベンチマークは既存のVLモデルの多くには適合しておらず、下流タスクに移行することなく、VLモデルの常識的知識を自動的に直接比較することは、未解決の課題である。 Contribution: 視覚言語モデルにおけるコモンセンス能力を改善する手法を提案 提案手法 知識グラフを利用しコモンセンスで補強された画像とテキストのペアを生成するデータ増強手法であるDANCEを提案 下図は知識グラフを利用したデータ生成法を示している。 原論文 Figure 2 から引用 💡 コモンセンス知識グラフ(ConceptNet)を(エンティティ、関係、エンティティ)の三つ組み形式にしそれらをエンティティの1つを含む画像と対にする。 💡 その画像に含まれるエンティティの名前を、例えば「このアイテム」のような指示代名詞で隠す。 💡 3つ組みから説明文を生成する。 生成されたデータは画像とテキストのペア形式のためほとんどのVLモデルの学習に容易に適用できる。 学習段階でエンティティ間の関係をモデルに記憶させることで、推論段階でそのようなデータ補強が不要。 データペア生成パイプラインは、既存の統合されたコモンセンス知識ベースと、視覚言語モデルの大規模かつ多様な学習データを活用し、自動的かつスケーラブルである。 ※ ConceptNet( ConceptNet ): 専門家、クラウドソーシング、ゲームなどさまざまなソースから作成された8Mのノードと21Mのエッジを持つ、一般的で統合されたコモンセンス知識グラフ。 検索にもとづくより広く適応可能な新しいコモンセンスベンチマークを提案 提案ベンチマークにはCOCOデータセットとConceptNetを用いて上記手順で生成された画像-テキストペアを利用する。 提案ベンチマークはテキスト-画像検索と画像-テキスト検索に分けられ、前者はコモンセンスを必要とする記述に最も合致する画像を検索するもので、後者はその逆。 既存の常識的知識を用いて新しい知識を推論する汎化能力をさらに評価するために、テスト集合を知識がトレーニング集合に現れるtest-seenと、対応する関係がトレーニングに存在しないtest-unseenに分割する。 (例えば「パイナップルがピザに乗っている」という知識と「ピザハットがピザのメーカーの1つである」という知識を学習していた場合、モデルが 「パイナップルはピザハットに必要かもしれない 」と推論できるか) 生成されたデータセット 下図は既存のVLデータセットと生成されたデータセット、およびConceptNetにおける品詞分析結果を示している。 既存のデータセット(COCOやCC 12M)のテキストで最も頻出する単語は名詞であり、対照的に、知識ベースConceptNetにはより多くの動詞があり、エンティティ間の関係に関する豊富な情報を含んでいる。こうした分布違いがVLモデルのコモンセンス能力欠如に繋がっていると考えられ、提案するDANCE拡張データは、既存のVLデータよりも有意に多くのコモンセンスを提供。 原論文 Figure 4 から引用 下図は既存のさまざまな知識ベースデータセットとの比較を示している。提案ベンチマークは規模が大きく、幅広い知識を含んいる。 原論文 Table 2 から引用 下図は提案ベンチマークにおける既存モデルと人手によるスコアの比較を示している。提案ベンチマークでは人手のスコアと既存モデルのスコアに大きな乖離がある。 原論文 Table 1 から引用 結果 下図は事前学習、ファインチューニングそれぞれにDANCEによるデータ増強を適用した場合のスコア比較を示している。いずれの場合もDANCEによるデータ増強によりスコアが改善していることがわかる。 また、test-unseenデータについても、大きな改善が観察される⇒DANCEの事前学習がモデルのコモンセンス能力を向上させるだけでなく、既存のコモンセンス知識に基づいて新しい知識に汎化する能力を強化することを示している。 加えて、コモンセンスをあまり含まないCOCO検索のバニラベンチマークでもその精度は維持されるか、それ以上の精度となる。このことは、DANCEがコモンセンス能力を高めると同時に、一般的な視覚言語表現を学習することを示している。 原論文 Table 3 から引用 下図は既存のコモンセンスベンチマーク(OK-VQA)での比較を示している。こちらもDANCEで事前学習したモデルでは精度が改善していることが分かる。 原論文 Table 4 から引用 上図は定性分析の結果を示している。右の画像(OK-VQA)の例では既存モデルが「風船を満たしているものは何か」という質問に正しく答えられていないのに対し、提案手法による事前学習を行ったモデルは正しく答えられている。 原論文 Figure 6 から引用 💡 実際ConceptNetを見ると以下のような知識があるのでこういったコモンセンスが活かされているのではと考えられる。 下図はより多くのベンチマークにおいて他のVLモデル(ALBEF)と比較した結果を示している。 提案ベンチマークでは大きな精度向上が見られるほか、VQA(標準的な視覚的質問応答)やNLVR(画像ペアに関するキャプションの真偽を分類)は特別コモンセンスを対象としているわけではないにもかかわらず精度が改善している。 原論文 Table 5 から引用 今後の課題 人間のような知能を実現するためには、常識的な知識を認識するだけでは不十分である。 ⇒モデルは、現実のシナリオにおける数学的・物理的計算のような推論ができなければならず、これは既存のVLモデルではまだ弱く、既存の常識的知識ベースには含まれていない。 RA-CLIP: Retrieval Augmented Contrastive Language-Image Pre-training 2 概要 背景: 自然言語と画像を結びつけて対比学習を行うCLIP手法は、色々なコンピュータービジョン分野のタスクにおいて優れた汎用性能があるため注目されている。 CLIPでは一定の精度を達成するために多くのデータから視覚的概念を学習(記憶)することが必要で、限られたデータでの精度向上が課題として挙げられている。 Contribution: 本論文では、RA-CLIPという手法を提案し、同じ学習データ量で大幅にzero-shot画像分類のタスクにおいて+12.7%(Top-1)の精度向上を実現。 RA-CLIP:より豊富な情報量を持たせるように画像特徴量を拡張する手法。 テキスト特徴量のほうは画像特徴量と比べて情報量が少ないため、拡張しても有用な情報量だけ(拡張によりノイズも入ってしまう)を増やすことが難しいとablation studyの実験によって判明。 提案手法 原論文 Figure 2 から引用 上図は全体の処理の流れを示している: 入力画像に対して、学習セットとは別の参照セットから関連画像とテキストの複数のペアをRAM(Retrieval Augmented Module)というモジュールで画像エンコーダーによって抽出した画像特徴量を拡張し、より豊富な情報量を持つ画像特徴量にすることで大幅なzero-shot精度の向上を実現。 テキスト側の処理はCLIPとは同じ、テキストエンコーダーで特徴量を抽出する。 RA-CLIPとオリジナルCLIPの違いをイメージしやすいようにたとえると、テスト段階でCLIPは暗記・理解できた概念にしか正しく答えられないという特徴に対して、RA-CLIPは問題に関連する参考情報を見ながらテストを受けられるので、限られた学習でさまざまな概念をきちんと理解できていなくても、CLIPと比較して得点が上がるという特徴があると考えられる。 入力画像と関連する画像・テキストペアの検索の実現: 入力画像と参照セットにおける画像の特徴量を教師なし学習したViTモデル(DINO)で抽出し、類似度が高い上位Kの画像と対応するテキストを参照セットから取得。また、学習段階ではこの特徴抽出モデルのパラメータは凍結される。 原論文 Figure 3 から引用 上図はRAMの処理の流れを示している: (ViT:DINO)と (Transformer:SentenceT)はそれぞれ事前に学習したシングルモーダルのエンコーダーで、パラメータはRA-CLIPの学習段階で更新されない。 と で参照セットから検索できた関連画像・テキストペアの特徴量 、 を抽出し、Multi-head Attention blockによって埋め込み特徴量 、 を生成し、最終的に拡張された を取得。 実験 データセット(baseline): 参照セット:YFCC15Mからランダムサンプリング(1.6 millionの画像・テキストペア) 学習セット:YFCC15Mその他のデータ(13 million の画像・テキストペア) 下図は学習データセットの例を示している。 出典: https://huggingface.co/datasets/Ziyang/yfcc15m テストデータ: ImageNetやCIFAR100といったimage classificationのテストデータセットでzero-shot推論 モデル構造: 画像エンコーダー:ViT-B/32 特徴量次元数768 テキストエンコーダー:BERT-base 特徴量次元数768 結果 下図は定量評価の結果を示している。 原論文 Table 1 から引用 原論文 Table 3 から引用 CLIPとRA-CLIPのbaseline(ID1 & ID5)では同じ量の学習データセットを利用していたが、zero-shotの画像分類テストの結果はRA-CLIPの方が精度が高く(+15.8%)、提案手法の有効性を示した。 複数の画像分類データセットにおいてzero-shot/linear probe(学習済みのエンコーダーを凍結して新たにclassification headを学習する)の平均精度がそれぞれ+12.7%/+6.9%向上され、提案手法の導入により汎用性能の向上を実現した。 原論文 Figure 5 から引用 下図は定性評価の結果を示している。RA-CLIPが正しく識別できたケースの参照セット検索プロセスと識別結果から、RAMが正しい参考情報を用いて入力画像の特徴量をより豊かにできることを示している。 原論文 Figure 4 から引用 上図は参照セットの規模とzero-shot画像分類精度の関連性を示している。水平軸が対数スケールでプロットされているため、参照セットを拡張し続けると性能が飽和になってしまう。 Pros&Cons Pros:「参考資料持ち込み可能なテスト」により、限られた学習データセットでCLIPモデルの精度向上が実現できる。 Cons:Vanilla CLIPと比較して、類似画像検索による特徴抽出や類似度算出の処理と、RAMモジュールの特徴量拡張処理(たとえるとテストの際に参考資料から関連情報を探すこと)が必要で、全体の計算量が増えてしまう。 Fine-tuned CLIP Models are Efficient Video Learners 3 概要 背景: CLIPやALIGNなどの事前学習済みの視覚言語(VL)モデルは、インターネットから集めてきた数億の画像・テキストペアを用いて学習し、分類、検出、セグメンテーションなどの多くのタスクにおいて強力な汎化性能とゼロショット能力を獲得した。しかし映像における情報量は画像より遥かに多いため、映像・テキストペアの学習データを用意するコストも膨大であり、映像タスクのためのCLIPをゼロから学習することはほぼ不可能である。従って、事前学習済みの画像言語モデルを映像ベースのタスクに適応することが必要となる。 最近の映像ベースのアプローチでは、空間的時間的モデリングのためにCLIPの表現を追加の学習可能なコンポーネントとして採用した。しかし、事前学習済みのCLIPエンコーダーをfine-tuneするとともに、新たに導入された時間モデリングコンポーネントがCLIPの汎化能力を妨げてしまう。 Contribution: 画像ベースのCLIPを映像のタスクに適応させるためのViFi-CLIP(Video Finetuned CLIP)と呼ばれるベースラインを提案。CLIPのfine-tuningが映像特有の帰納バイアスを学習するのに十分であることを示した。 提案手法はzero-shot、base-to-novel generalization、few-shot、fully-supervised tasksを含む4つの異なる設定で実験した結果、SotA手法より優れた性能を示した。 また、論文で提案した「Bridge&prompt」手法はアノテーション済みの学習データが少ない領域において、fine-tuningとプロンプト学習によりモダリティギャップを埋めることに成功し、手法の有効性を示した。 提案手法 下図は提案手法の処理流れを示している。 原論文 Figure 3 から引用 CLIPの汎化性能を低下させるコンポーネントを新規追加せず、temporal pooling(average pooling)を用いた単純なフレームレベルの後期特徴集約により、CLIPの出力特徴量の時間的情報の取りまとめを実現。 テキストエンコーダーでは、映像を表すプロンプト(例えば”a photo of a ”)を1つの埋め込み特徴量に変換し、映像との対応関係を利用して対比学習を行った。 ViFi-CLIPでは、画像エンコーダーとテキストエンコーダー両方でfull fine-tuningを実施 実験 ViFi-CLIPの汎化能力を分析するために、2つの問題設定で評価する: Zero-shot settingによるクロスデータセットの汎化性能の評価 モデルはソースデータセットで訓練され、そのままダウンストリームの異なるデータセットに転移され評価する。 Base-to-novel settingによる新しいクラスでの汎化性能の評価 提案されたベースと新しいクラスの分割は、全カテゴリを均等な2つのグループに分け、最も頻繁に発生するクラスをベースクラスとしてグループ化する。モデルはベースクラスで学習され、ベースおよび新しいクラスの両方で評価する。 Few-shot setting データセットからK-shotのデータがランダムサンプリングされ学習に利用される。データセットのvalidation setで評価する。 Fully-supervised setting データセットの全てのtraining setで学習し、test setで評価する。 結果 下図は定量評価の結果を示している。 原論文 Table 1, Table 2 から引用 ViFi-CLIPの汎化性能が従来手法より高い Zero-shot settingでは、ゼロショットアクション認識に特化したシングルモーダル手法と画像ベースのマルチモーダルVLモデルを映像行動認識に適応させたモデルと比較して、ViFi-CLIPの方がクロスドメインでの精度が高い Base-to-novel settingでは、 帰納バイアスを利用して コンポーネント を追加したモデルと比較して、 ViFi-CLIPの方がベース精度と新しいクラスでの精度が高い 原論文 Table 3 から引用 原論文 Table 4 から引用 ViFi-CLIPの教師あり学習の性能も従来手法より優れている(または同レベル) Few-shot settingでは、ViFi-CLIPはshot数(K)の増加とともに精度が上がる傾向があり、全てのショットで従来手法より精度が優れていることを示している Fully-supervised settingでは、ViFi-CLIPは時間モデリングのために追加で設計された学習可能なコンポーネントを使用する手法と比較して同レベルの精度を達成 下図は定性評価の結果を示している。 原論文 Figure 1 から引用 ViFi-CLIPの埋め込みは、より良い分離が実現され、CLIPの単純なfine-tuningだけでも適切な帰納バイアスを学習し、映像内の時間情報をモデル化するために専用のコンポーネントを持つ手法に対して競争力のある性能を発揮できることを示している 原論文 Figure 4 から引用 ViFi-CLIPは、時間的手がかりから物体間の関係やシーンのダイナミクスを学習し、高速移動する部分と物体に焦点を当てることで、ビデオ固有の情報をエンコードする能力を示している 下図は処理性能の評価結果を示している。 原論文 Table 5 から引用 余計なコンポーネントを使用していないため、他の手法と比較してFLOPsが低く、トレーニングパラメータの規模も少なくなる。 Pros&Cons Pros:本論文で提案したViFi-CLIPのベースラインでは、ほとんどCLIPの構造を改変せず、シンプルなfine-tuningだけでもVanilla CLIPを映像ドメインに適応させることができる。精度と処理性能の両方において、映像内の時間情報をモデル化するために専用のコンポーネントを持つ従来手法より優れている。 Cons:専用のコンポーネントを持つ手法と比較して、zero-shotの映像認識タスクにおいて汎化性能が優れている一方、教師あり学習の場合だと性能が下がることが確認される。 Top-Down Visual Attention from Analysis by Synthesis 4 概要 背景: 人間の注意方法 トップダウン型注意:選ぶべき事前知識を持ち、注目すべきものをピックアップしそれ以外の情報を省くようにバイアスを掛けて見つけ出すこと ボトムアップ型注意:事前知識なく、他より明らかに目立つもの、異質なものなどを見つけ出すこと 先行研究では人の知覚システムにおけるボトムアップ型注意のメカニズムは合成による分析( Analysis by Synthesis )を実行した結果であるという仮説が立てられている 入力画像と画像の潜在的原因に関する高レベルの事前分布の両方に依存 Analysis by Synthesisを通して異なるオブジェクトの低レベルの認識を事前知識として持ち、トップダウンの知識として定式化される 既存の研究は概念的なのでモデル設定の指針になりにくかった Contribution: 人間の視覚的なトップダウン型の注意方法とされているAnalysis by Synthesisを取り入れた手法(AbSViT, Analysis-by-Synthesis Vision Transformer)の利用により、VQA(Vision Q&A)やゼロショット検索といった画像に対する質問に関連する部分をアテンションするタスク、画像認識、セグメンテーションタスクで精度向上が実現できた。 提案手法 下図は提案手法の処理流れを示している。 (a) ・各ステップの操作は紫色、その他は灰色で色分けする。 ・AbsviTはまず画像をフィードフォワード経路に通す。 ・出力トークンは事前ベクトルξとの類似度によって重み付けす。 ・デコーダを通して各自己注意モジュールにフィードバックされ、最終フィードフォワード実行のトップダウン入力となる。 (b) ・自己注意へのトップダウン入力は値行列に加えられるが、他の部分は変わらない。 原論文 Figure 3 から引用 下図は定性評価の結果を示している。 各画像に対して、ボトムアップ注意は両方の物体を強調する。 これに対して、異なるクラスプロトタイプを事前学習として用いることで、異なる物体に注目するようにトップダウン注意を制御でき、それに応じて分類結果も変化する。 原論文 Figure 4 から引用 データセット VQAについては、VQAv2をトレーニングおよびテストに使用し、VQA-HATによって収集された人間の注意と注意マップを比較する。ゼロショット画像検索にはFlickr30Kを使用する。 画像分類については、ImageNet-1K(IN)で学習とテストを行い、IN-Cの破損画像、IN-Aの敵対的画像、IN-RとIN-SKの分布外画像でもテストを行う。セマンティックセグメンテーションについては、PASCAL VOC、Cityscapes、ADE20Kでテストしている。 下図はVision-Language Taskの結果を示している。 原論文 Table 1 から引用 原論文 Figure 6 から引用 下図は画像の分類, ロバスト性の結果を示している。 原論文 Table 2 から引用 原論文 Figure 7 から引用 トップダウンのアテンション設計から得られるオブジェクト中心の表現は、破損した画像、敵対的な画像、分布外の画像に対する汎化を可能にする。 結論 著者らは、視覚的なトップダウン型の注意方法であるAbS(Analysis-by-Synthesis)とスパース再構成の機能的等価性に関する先行研究から、 目的志向的なトップダウン変調を行うことで、AbSと同様のスパース再構築を最適化することを示した。その結果、トップダウン型の注意を再現できることを示した。 また、著者らは、AbSを変動的に近似するトップダウン変調ViTモデルであるAbSViTを提案した。AbsViTは制御可能なトップダウン注意を達成し、V&Lタスク、画像分類、ロバスト性においてベースラインよりも改善することを示した。 新たなタスクを提案した論文 Connecting Vision and Language with Video Localized Narratives 5 概要 Vision&Languageのこれまでの研究 Image Captioning:静止画に対して、キャプションの(一部の)単語をGrounding(結びつけ) Localized Narrative :アノテーターが自分で画像を説明しながら、説明している領域をマウスで指定→音声とマウスポインタが同期しているため、各単語の視覚的なGroundingが正確に行える Video Localized Narratives(本論文):静止画→動画に拡張 静止画と動画の違い 静止画:ある一瞬のみ記述 VS 動画:オブジェクト間の関係性や相互作用など一連のイベントを記述 動画の場合より詳細なNarrativeをアノテーション可能である。動画の脈略を参照できる可能性が増え、対象物のco-reference(共参照)問題の解決にもつながる(e.g. 同一の名詞(オウム)がNarrative中に複数回出現し、かつ異なるインスタンスを示す場合、オウムの細かな見た目の違いや動作などで参照先の曖昧性を回避できる) Contribution: Video Localized Narrrativesをアノテーションするプロトコルを提案 アクター(動画の登場人物;人、オウム、背景など)ごとに説明 受動関係が明確になることで複雑なイベントを正確に記述(e.g. 人がオウムを触る/オウムが人に触られている) キーフレームごとに説明 動画に対して説明しようとすると、顕著なオブジェクト(主役)のみを記述する可能性が高い 作成したデータセットをVideo Narrative Grounding (VNG)とVideo Question Answering (VQA)へ適用 従来のVideo Narrative Grounding (VNG) 下図はVNGのタスク定義を示している。 入力:映像、説明文(Narrative)、説明文中の名詞の位置 出力:参照されたオブジェクトのフレームごとのセグメンテーションマスク 原論文 Figure 5 から引用 提案手法 ベースライン( ReferFormer )を拡張 下図はReferFormerの処理流れを示している。 ReferFormerは映像から特徴を抽出するためのVisual Encoderと、説明文から特徴(条件付きクエリ特徴)を抽出するためのText Encoderで構成される。 Text Encoderから抽出した 単語ごとの特徴量 ( )をプールして、文全体の特徴量( )とする これらの2つのモーダルの特徴量をDecodeしてフレーム毎のセグメンテーションマスクを得る 原論文 Figure 2 から引用 ReferFormerの課題 文全体の特徴を用いているが、これが有効なのは「文全体が1つのオブジェクトを記述する場合」 VNGでは1つの動画に対して複数のアクター(オブジェクト)が存在するためそぐわない ReferFormer-VNG(提案手法) アクターごとの Narrativeの名詞に限定して 単語ごとの特徴量( )を抽出、それらをプールして文全体の特徴量とする。 2つの異なるオウムをセグメンテーションに分ける場合、最初の「オウム」のNarrativeの名詞に対してReferFormer-VNGを実行し、次の「オウム」は(1回目と異なる)Narrativeの名詞の特徴を使って2回目に実行する。 本論文ではMouse Traceは学習・評価には使っていない。VQAデータセットを作成する際のみに使用。 データセット 下図はVNGのデータセットを示している。既存データセットに対して、物体間の相互関係を含む状況説明(narrative)とその場所(マウス位置)を付与する。 原論文 Table 1 から引用 下図はOVIS/UVO-ViDLN(提案データセット)を示している。: OVISとUVOに対して、状況説明(narrative)とその場所(マウス位置)を付与する すべての単語(形容詞、動詞を含む)をマウス位置をトレースしてGrounding 原論文 Figure S1 から引用 下図は OVIS を示している。 video segmentation用データセット(e.g. person, fish, vehicle, horse, sheep…) 出典: http://songbai.site/ovis/ 下図は UVO を示している。 video segmentation用データセット(e.g. person, car, chair, bottle…) 出典: UVO論文 Figure 3 Ego4D 一人称視点のみ 一部の名刺のみを矩形でGrounding Epic-Kitchens キッチン内のみ すべての名詞をピクセル単位でGrounding 実験 評価尺度: & Measure と の平均 :空間的な正しさ(認識結果とGroundTruthのマスクのIoU) :クラスの正しさ(各マスクのクラス認識結果のF値; PrecisionとRecallの調和平均) データセット OVIS-ViDLN, UVO-ViDLN 加算名詞のみを採用(e.g. car, parrot)し、stuff categories(e.g. sky, water)は除外。 Method/Dataset OVIS UVO Baseline(Full narrative) 22.9 25.8 Baseline(Noun) 25.7 35.6 Proposed(Best) 32.7 46.4 結論 著者らは、動画に対するキャプショングを解くためのデータアノテーションのプロトコルとデータセットを構築し、ベースラインの手法を提案した。 手法自体はシンプルで効果的ではあるものの、アクターの数だけReferFomer-VNGを実行するために複雑な説明文に対する計算コストが高い。 Visual Programming: Compositional visual reasoning without training 6 概要 背景: これまでの汎用AIシステムを構築するための主なアプローチはエンド・ツー・エンドで学習可能なモデルを用いた大規模な教師なし事前学習と、それに続く教師ありマルチタスク学習だった。しかしこのアプローチでは、各タスク用に整備されたデータセットが必要なので、汎用AIシステムに複雑なタスクを実行させるには、無限とも言えるデータセットが必要になる。したがって、このアプローチで汎用AIシステムを複雑なタスクが実行できるように拡張することは困難である。 例えばテレビ番組「ビッグバン★セオリー」に登場する7人のメインキャラクターをこの画像にタグ付けするというタスクを考える。 このタスクを実行するために、システムはまず指示の意図を理解し、顔を検出し、知識ベースからビッグバン★セオリーの主要登場人物のリストを取得し、登場人物のリストを使用して顔を分類し、認識された登場人物の顔と名前を画像にタグ付けするという一連のステップを実行する必要がある。 これらの各ステップを実行するさまざまな視覚システムや言語システムが存在するが、自然言語で記述されたこのタスク全体を実行することは、現状のエンド・ツー・エンドで訓練されたシステムの範囲を超えている。 出典: https://cvpr2023.thecvf.com/media/cvpr-2023/Slides/22652.pdf Contribution: 人々が実行したいと思うような複雑で多様なタスクに対応する汎用AIシステムを提案 提案手法 大規模言語モデルの文脈内学習能力を利用し、タスク固有のトレーニングを必要とせず、自然言語で記述されたタスクを、エンド・ツー・エンドに特化した学習済みモデルや他のプログラムで処理できるような単純なステップに分解することで、複雑で幅広いタスクに対応できる、 VISPROG というシステムを提案。 VISPROGは、ビジュアルデータ(単一の画像または画像のセット)と自然言語命令の入力からステップのシーケンス(VISPROGプログラム)を生成し、それを実行して解と包括的で解釈可能な根拠の両方を得る。 生成されたプログラムの各行は、市販のコンピュータビジョンモデル、画像処理サブルーチン、またはpython関数など幅広いモジュールのうちの1つを呼び出し実行することでプログラムの後続部分で消費される可能性のある中間出力を生成する。 下図の「Factual Knowledge Object Tagging」の例では、VISPROGによって生成された視覚的プログラムは、顔検出器、知識検索システムとしてのGPT-3、およびオープン語彙画像分類器としてのCLIPを呼び出して、目的の出力を生成する。 原論文 Figure 1 から引用 LLMによるプログラム生成 VISPROGは、GPT-3に自然言語で記述された命令と希望する高レベルプログラムの例をプロンプトし、GPT-3の文脈内学習能力を利用して、実際の命令用のプログラムを出力する。 下図は画像編集タスクに対するプロンプトを示している。これらコンテキスト内のプログラム例は手動で書かれており、通常、画像を添付することなく構築可能。 VISPROG プログラムの各行(プログラムステップ)は、モジュール名、モジュールの入力引数名とその値、出力変数名で構成される。GPT-3が各モジュールの入出力タイプや機能を理解できるように、説明的なモジュール名(例:"Select"、"ColorPop"、"Replace")、引数名(例:"image"、"object"、"query")、変数名(例:"IMAGE"、"OBJ")を使用。 これらのインコンテキストの例は、新しい自然言語命令とともにGPT-3に供給され、画像やその内容を観察することなく、VISPROGは入力画像上で実行可能なプログラム(下図の後段)を生成し、記述されたタスクを実行する。 原論文 Figure 3 から引用 モジュール 下図はVISPROGで実現可能な映像解析処理を示している。 VISPROGは現在、画像理解、画像操作(生成を含む)、知識検索、算術・論理演算などの機能を実現する20のモジュールをサポートしている。 原論文 Figure 5 から引用 VISPROGでは、各モジュールは下図の通りPythonクラスとして実装され、 (i)行を解析して入力引数名と値、出力変数名を抽出する (ii)学習済みニューラルモデルを含む可能性のある必要な処理を実行し、出力変数名と値でプログラム状態を更新する (iii)VISPROGの処理の流れを視覚的に確認できる メソッドを持つ。 モジュールクラスを実装して登録するだけでVISPROG に新しいモジュールを追加することもできる。 原論文 Code 1 から引用 プログラムの実行 プログラムの実行はインタープリターが行う。インタープリターは、以下のような流れで動作する。 (i)プログラムの状態(変数名とその値を対応付けた辞書)を入力で初期化 (ii)その行で指定された入力で正しいモジュールを呼び出しながら、プログラムを行ごとにステップ実行 (iii)各ステップの実行後、プログラム状態をステップの出力名と値で更新 視覚的根拠の提示 各モジュールクラスには、モジュールの入力と出力をHTMLスニペットで視覚的に要約するメソッドも用意されている。 インタープリターは、すべてのプログラムステップのHTML要約を繋ぎ合わせて、プログラムの論理的正しさを分析し最終的な出力を検査するために使用できる視覚的根拠(下図)を提示できる。 こうした視覚的な根拠は、ユーザーが失敗の理由を理解し、パフォーマンスを向上させるために自然言語命令を最小限に調整することも可能にする。 原論文 Figure 4 から引用 実験 VISPROGは、多様で複雑な視覚タスクに適用できる柔軟なフレームワークを提供する。 本実験では空間推論、複数画像に関する推論、知識検索、画像生成と操作の4つのタスクで評価を実施した。 各タスクで使用された入力、出力、およびモジュールは下図の通り。 原論文 Figure 5 から引用 Compositional Visual Question Answering VISPROGの構成的で多段階の視覚的質問応答タスク(GQA)への適性を確認した。 GQAタスクのためのモジュールには、オープン語彙のローカライズ、VQAモジュール、バウンディングボックスの座標や空間的前置詞(above、leftなど)が与えられた画像領域を切り取る関数、ボックスを数えるモジュール、Python式を評価するモジュールなどがある。 例えば、「小さなトラックは、ヘルメットをかぶっている人々の左側にあるか、右側にあるか?」というような質問に対して、VISPROGは、まず「ヘルメットをかぶっている人々」をローカライズし、その人々の左側(または右側)の領域を切り出し、その側に「小型トラック」があるかどうかをチェックし、あれば「left」、なければ「right」を返す。 プロンプト作成ではトレーニングセットから31のランダムな質問に、希望するVISPROGプログラムを手動でアノテーションする。GPT-3には、GQAの各質問に回答するコストを削減するために、このリストからランダムに抽出された、より少ないサブセットを提供する。 Reasoning on Image Pairs (NLVR) VQAモデルは単一の画像に関する質問に答えるように学習されるが、実際には、画像コレクションに関する質問に答えるシステムが必要とされるかもしれない。 例えば、あるユーザーが休暇中の写真アルバムを解析し、次のような質問に答えるようシステムに求めた場合:「エッフェル塔を見た翌日、私たちはどのランドマークを訪れたか?」 VISPROGが複数画像の学習データで訓練することなく、単一画像VQAシステムを使用して、複数画像を含むタスクを解決する能力をNLVRベンチマークで確認する。 通常、NLVRの課題に取り組むには、画像ペアを入力とするカスタム・アーキテクチャをNLVRの訓練セットで訓練する必要がある。対してVISPROGは、複雑なステートメントを、個々の画像に関するより単純な質問と、算術演算子および論理演算子を含むpython式と、画像レベルの質問に対する回答に分解することでこれを実現する。 プロンプト作成ではNLVRの訓練セットで、16のランダムなステートメントについてVISPROGプログラムをアノテーションする。これらの例のいくつかは冗長(類似したプログラム構造)であるため、4つの冗長なものを削除して12例のキュレートされたサブセットも作成する。 Factual Knowledge Object Tagging 名前も知らない画像の中の人物や物体(例えば有名人、企業のロゴ、人気の車とそのメーカー、生物の種等)を識別したい状況において、こうしたタスクを解決するには、人物、顔、物体をローカライズするだけでなく、外部の知識ベースで事実知識を調べ、分類のためのカテゴリーセットを構築する必要がある。 このタスクを、事実知識オブジェクト・タギング(Factual Knowledge Object Tagging)と呼び、VISPROGはGPT-3を暗黙の知識ベースとして使用する。例えばGPT-3に”テレビ番組「ビッグバン★セオリー」の主な登場人物をカンマ区切りで列挙せよ”といった自然言語プロンプトで問い合わせることでカテゴリーセットを生成し、得られたカテゴリーセットを、ローカリゼーションや顔検出モジュールによって生成された画像領域を分類するCLIP画像分類モジュールの分類先として利用する。 このタスク用には14のインコンテキストのプログラム例を作成する。(これらのインコンテキストプログラム例には画像は関連付けられていない) Image Editing with Natural Language テキストからの画像の生成はStable Diffusionなどのモデルにより、ここ数年で目覚ましい進歩を遂げているが、「Daniel Craigの顔を:pで隠す」(非特定化またはプライバシー保護)、「Daniel Craigのカラーポップを作成し、背景をぼかす」(オブジェクトの強調表示)などのプロンプトを処理することは、まだ難しい。 こうしたオブジェクトの置き換え等のような高度な編集を実現するには、まず関心のあるオブジェクトを特定し、置き換えるオブジェクトのマスクを生成し、元の画像とマスクおよびその位置に生成する新しいピクセルの説明を使用して、画像インペインティングモデル(本研究ではStable Diffusion)を呼び出す必要がある。VISPROGは、必要なモジュールとサンプルプログラムを備えていれば、こうした非常に複雑な命令を簡単に扱うことができる。 このタスクでは知識タグ付けと同様に、関連する画像のないインコンテキストの例を 10 個作成する。 結果 プロンプトサイズの影響 GQAとNLVRでは、インコンテキストの例が多いほど性能が向上することが下図から分かる。 原論文 Figure 6 から引用 また、NLVRでは、VISPROGの性能はGQAよりも少ないプロンプトで飽和しているが、これはNLVRのプログラムが必要とするモジュールがGQAよりも少なく、それらのモジュールを使用するためのデモがGQAよりも少ないためであると考えられる。 汎用能力 下図は各タスクにおけるVISPROGの結果を示している。 GQAとNLVRではプロンプト戦略を変更した場合の結果を合わせて報告している。また、知識タギングと画像編集ではプロンプトとして与える自然文命令のチューニングを行った場合の結果も報告している。 原論文 Table 1, Table 2, Table 3, Table 4 から引用 前者2つではVILTモデルとの比較を行っており、GQAではVILTモデルの性能を上回っている。NLVRでは性能が下回っているが、VISPROGはゼロショットでNLVRタスクを実行するのに対して、VILT-NLVRはNVLRでファインチューニングされているので、VILT-NLVRでの結果は性能の上限の目安であり、VISPROGがそれに近い精度であることがわかる。(GQAの方で精度が上回っているのはVISPROGが利用しているVQAモジュールがVILT-VQAのため) 後者2つでは既存モデルでは単体でこのようなタスクを行えるモデルがないことから特定のモデルとの比較はされていないが、一定のレベルでこれらタスクを実行できることと、命令チューニングによってさらなる性能向上が可能なことを示している。下図はVISPROGの現在のモジュールセットで可能な画像編集の例であり、幅広い操作が可能であることを示している。 原論文 Figure 7 から引用 視覚的根拠の有用性 VISPROG による視覚的根拠は、失敗例の徹底的な分析を可能にする。 下図は今回の4タスクにおいて約100サンプルずつエラー分析した結果を示している。GQAでは誤ったプログラムがエラーの主な原因であり、サンプルの16%に影響を及ぼしているということを示している。このことから、失敗した問題に類似した、より多くのインコンテクスト例を提供することによって、エラー発生率が改善される可能性があることが分かる。同様にNLVRではVQAモデルをNLVR用のより優れたVQAモデルに置き換えることで、知識タギングや画像編集タスクでは「リスト」と「選択」モジュールの実装に使用されるモデルを改善することで、エラーを大きく減らせると考えられる。 原論文 Figure 9 から引用 また、下図は視覚的根拠によって明らかになったローカリゼーションエラーが、ローカリゼーションモジュールにとってより良いクエリになるように、ユーザがどのように命令を修正するかを示した例である。(他にも例えば、知識検索のためのより良いクエリを提供することや、Selectモジュールのためのカテゴリ名を提供して、そのカテゴリに属するセグメント化された領域に検索を制限することが含まれる) 実際に知識タギングや画像編集の結果を見ると命令チューニングが性能向上に有効であることがわかる。 原論文 Figure 8 から引用 今後の課題 VISPROGのような汎用視覚システムの性能をさらに向上させるためにはユーザーのフィードバックを取り入れる新しい方法の研究が必要である。 最後に 本記事では、マルチモーダルAIに関するCVPR2023の論文をいくつかピックアップしてご紹介しました。NTT Comは今までユニモーダルAIの研究開発をメインに取り組んで、自動翻訳サービスCOTOHA Translatorや議事メモ作成をサポートするCOTOHA Meeting Assist、映像解析ソリューションCOTOHA Takumi Eyesといったサービスを展開してきました。今後はChatGPTなどのマルチモーダルAI技術の活用も視野に入れて、実証実験や研究開発を進めていきます。 Ye, S., Xie, Y., Chen, D., Xu, Y., Yuan, L., Zhu, C., Liao, J. Improving Commonsense in Vision-Language Models via Knowledge Graph Riddles. In CVPR2023. ↩ Xie, C. W., Sun, S., Xiong, X., Zheng, Y., Zhao, D., Zhou, J. RA-CLIP: Retrieval Augmented Contrastive Language-Image Pre-Training. In CVPR2023. ↩ Rasheed, H., Khattak, M. U., Maaz, M., Khan, S., Khan, F. S. Fine-tuned clip models are efficient video learners. In CVPR2023. ↩ Shi, B., Darrell, T., Wang, X. Top-Down Visual Attention from Analysis by Synthesis. In CVPR2023. ↩ Voigtlaender, P., Changpinyo, S., Pont-Tuset, J., Soricut, R., Ferrari, V. Connecting Vision and Language with Video Localized Narratives. In CVPR2023. ↩ Gupta, T., & Kembhavi, A. Visual programming: Compositional visual reasoning without training. In CVPR2023. ↩
アバター
TOC サマリ 概要 検証 - IP Precedence 条件による TE(L3VPN Per-Flow Steering) - 検証項目とトポロジー 検証手順 1. Underlay & VPN & メトリック の設定 2. LSP の定義 3. ip-filter の定義 4. ip-filter を VPN の ingress interface に適用 5. ip-filter、LSP が適用されている事を確認 6. 疎通確認 検証 - CoS 条件による TE(L2VPN Per-Flow Steering) - 検証項目とトポロジー SR OS を PE として用いる際の L2VPN Per-Flow Steering の実現方法 検証手順 1. Underlay & メトリック の設定 2. EVI の設定 3. PXC の設定 4. PXC を用いた EVI 間の接続 5. LSP の定義 6. EVI と LSP の関連付け 7. mac-filter の定義と EVI への適用 8. 疎通確認 まとめ サマリ SR-MPLS で構成されたネットワークにおいて、Per-Flow Steering を実現 SR OS で IP Precedence に基づいた Traffic Engineering (TE) の検証に成功 SR OS で Class of Service (CoS) に基づいた TE の検証に成功 この記事は Multi-AS Segment Routing 検証連載の第 17 回です。目次は こちら 概要 イノベーションセンターの岩田です。普段の業務では Multi-AS Segment Routing に関する技術検証や、ベンチャー企業への技術支援でスマートフォンアプリケーション開発業務などを行なっています。 第 8 回 の記事で IOS XR + Junos の 2 つのベンダー機器で構成される L3VPN において 指定した 5-tuple や IP Precedence に関する条件に従って TE を実現する方法を紹介しました。 今回は新たに Nokia SR OS(Service Router Operating System)を加えた 3 つのベンダー機器から構成される環境において、IP Precedence 条件に従う TE と、CoS 条件に従う TE を実現する方法を紹介します。 検証 - IP Precedence 条件による TE(L3VPN Per-Flow Steering) - 検証項目とトポロジー 以下のような構成で、IP Precedence に基づく TE が実現できるかを検証します。 なお本記事では下記のバージョンで検証しました。 rt01: SR OS 22.7.R1 rt02: IOS XR 7.4.1 rt03: Junos 21.3R1.9 rt04: SR OS 22.7.R1 検証手順 以下の手順で検証します。 Underlay & VPN & メトリック の設定 LSP の定義 ip-filter の定義 ip-filter を VPN の ingress interface に適用 ip-filter、LSP が適用されている事を確認 疎通確認 1. Underlay & VPN & メトリック の設定 設定は省略します。Underlay & VPN の設定は 第 12 回 を、メトリックの設定は 第 15 回 の記事をそれぞれ参照ください。 以下のように L3VPN が構築できている事を確認します。 rt01 [ro:/configure] A:user@rt01# /show router bgp routes vpn-ipv4 =============================================================================== BGP Router ID:10.255.0.1 AS:65000 Local AS:65000 =============================================================================== Legend - Status codes : u - used, s - suppressed, h - history, d - decayed, * - valid l - leaked, x - stale, > - best, b - backup, p - purge Origin codes : i - IGP, e - EGP, ? - incomplete =============================================================================== BGP VPN-IPv4 Routes =============================================================================== Flag Network LocalPref MED Nexthop (Router) Path-Id IGP Cost As-Path Label ------------------------------------------------------------------------------- u*>i 65000:100:192.168.123.0/24 100 None 10.255.0.4 None 20 No As-Path 524284 u*>i 65000:100:192.168.123.254/32 100 0 10.255.0.4 None 20 No As-Path 524284 ------------------------------------------------------------------------------- Routes : 2 =============================================================================== rt04 [/] A:user@rt04# /show router bgp routes vpn-ipv4 =============================================================================== BGP Router ID:10.255.0.4 AS:65000 Local AS:65000 =============================================================================== Legend - Status codes : u - used, s - suppressed, h - history, d - decayed, * - valid l - leaked, x - stale, > - best, b - backup, p - purge Origin codes : i - IGP, e - EGP, ? - incomplete =============================================================================== BGP VPN-IPv4 Routes =============================================================================== Flag Network LocalPref MED Nexthop (Router) Path-Id IGP Cost As-Path Label ------------------------------------------------------------------------------- u*>i 65000:100:192.168.23.0/24 100 None 10.255.0.1 None 20 No As-Path 524284 u*>i 65000:100:192.168.23.254/32 100 0 10.255.0.1 None 20 No As-Path 524284 ------------------------------------------------------------------------------- Routes : 2 =============================================================================== 2. LSP の定義 IP Precedence が 3 の時に適用する LSP を設定します。 [ex:/configure router "Base" mpls] A:user@rt01# info path "rt01_rt03_rt04" { admin-state enable hop 10 { sid-label 16003 } hop 20 { sid-label 16004 } } lsp "for_precedence3" { admin-state enable type p2p-sr-te to 10.254.0.4 primary "rt01_rt03_rt04" { } } 3. ip-filter の定義 受信したパケットの IP Precedence の値に応じて適切な LSP を適用するために ip-filter を定義します。 [ex:/configure] A:user@rt01# info filter ip-filter for_cust-a filter-id 1 entry 1 { match { dscp cs3 } action { forward { vprn-target { bgp-nh 10.255.0.4 vprn "cust-a" lsp "for_precedence3" } } } } entry 100 { action { accept } } 4. ip-filter を VPN の ingress interface に適用 定義した ip-filter を cust-a の sap(service access point) の ingress パラメータとして適用します。 [gl:/configure] A:user@rt01# /info service vprn "cust-a" interface "to_cust-a" sap 1/1/c3/1:0 admin-state enable ingress { filter { ip "for_cust-a" } } 5. ip-filter、LSP が適用されている事を確認 定義した filter が適用されていることと、パケットの転送先が指定した LSP になっていることを確認します。 [/] A:user@rt01# show filter ip "for_cust-a" | no-more =============================================================================== IP Filter =============================================================================== Filter Id : 1 Applied : Yes Scope : Template Def. Action : Drop Type : Normal Shared Policer : Off System filter : Unchained Radius Ins Pt : n/a CrCtl. Ins Pt : n/a RadSh. Ins Pt : n/a PccRl. Ins Pt : n/a Entries : 3 Sub-Entries : 4 Description : (Not Specified) Filter Name : for_cust-a ------------------------------------------------------------------------------- Filter Match Criteria : IP ------------------------------------------------------------------------------- Entry : 1 Description : (Not Specified) Log Id : n/a Src. IP : 0.0.0.0/0 Src. Port : n/a Dest. IP : 0.0.0.0/0 Dest. Port : n/a Protocol : Undefined Dscp : cs3 ICMP Type : Undefined ICMP Code : Undefined Fragment : Off Src Route Opt : Off Sampling : Off Int. Sampling : On IP-Option : 0/0 Multiple Option: Off Tcp-flag : (Not Specified) Option-pres : Off Egress PBR : Disabled Primary Action : Forward (VPRN Target) BGP NH Address : 10.255.0.4 Router : 100 LSP : for_precedence3 Service Label : 16 Extended Action : None Secondary Action : None PBR Down Action : Forward (entry-default) Downloaded Action : Primary Dest. Stickiness : None Hold Remain : 0 Ing. Matches : 0 pkts Egr. Matches : 0 pkts Entry : 100 Description : (Not Specified) Log Id : n/a Src. IP : 0.0.0.0/0 Src. Port : n/a Dest. IP : 0.0.0.0/0 Dest. Port : n/a Protocol : Undefined Dscp : Undefined ICMP Type : Undefined ICMP Code : Undefined Fragment : Off Src Route Opt : Off Sampling : Off Int. Sampling : On IP-Option : 0/0 Multiple Option: Off Tcp-flag : (Not Specified) Option-pres : Off Egress PBR : Disabled Primary Action : Forward Ing. Matches : 75 pkts (5970 bytes) Egr. Matches : 0 pkts =============================================================================== また、LSP が Up していることも確認します。 [/] A:user@rt01# show router mpls sr-te-lsp =============================================================================== MPLS SR-TE LSPs (Originating) =============================================================================== LSP Name Tun Protect Adm Opr To Id Path ------------------------------------------------------------------------------- for_precedence3 4 N/A Up Up 10.254.0.4 ------------------------------------------------------------------------------- LSPs : 1 =============================================================================== 6. 疎通確認 以下のように、想定した TE を実現できている事が確認できました。 IP Precedence が 3 の時の TE 結果 user@vm01:~$ traceroute 192.168.123.1 -t 96 -n -q 1 traceroute to 192.168.123.1 (192.168.123.1), 30 hops max, 60 byte packets 1 192.168.23.254 1.997 ms 2 10.1.3.2 3.714 ms 3 192.168.123.254 3.606 ms 4 192.168.123.1 3.549 ms それ以外のパケットの時の TE 結果 user@vm01:~$ traceroute 192.168.123.1 -n -q 1 traceroute to 192.168.123.1 (192.168.123.1), 30 hops max, 60 byte packets 1 192.168.23.254 1.192 ms 2 10.1.2.2 2.918 ms 3 192.168.123.254 2.497 ms 4 192.168.123.1 2.484 ms また、パケットカウンタを確認すると受信したパケットが各 entry に振り分けられたことが確認できます。 [/] A:user@rt01# show filter ip "for_cust-a" counters =============================================================================== IP Filter =============================================================================== Filter Id : 1 Applied : Yes Scope : Template Def. Action : Drop Type : Normal Shared Policer : Off System filter : Unchained Radius Ins Pt : n/a CrCtl. Ins Pt : n/a RadSh. Ins Pt : n/a PccRl. Ins Pt : n/a Entries : 3 Sub-Entries : 4 Description : (Not Specified) Filter Name : for_cust-a ------------------------------------------------------------------------------- Filter Match Criteria : IP ------------------------------------------------------------------------------- Entry : 1 Ing. Matches : 25 pkts (1950 bytes) Egr. Matches : 0 pkts Entry : 100 Ing. Matches : 100 pkts (7920 bytes) Egr. Matches : 0 pkts =============================================================================== 検証 - CoS 条件による TE(L2VPN Per-Flow Steering) - 検証項目とトポロジー 以下のような構成の L2VPN 上で、CoS(Class of Service)に基づく TE を実現できるかを検証します。 なお本記事では下記のバージョンで検証しました。 rt01: SR OS 22.7.R1 rt02: IOS XR 7.4.1 rt03: Junos 21.3R1.9 rt04: Junos 21.3R1.9 SR OS を PE として用いる際の L2VPN Per-Flow Steering の実現方法 SR OS において、L2VPN Per-Flow Steering を行うためのコマンドや機能はないため、 以下に示す構成のように、複数の EVPN Instance (EVI) とインスタンス間を接続するための port cross-connect(PXC)、mac-filter の機能を組み合わせて実現する必要があります。 rt01 において、対向 PE(rt04)から送られてきたパケットは pbf において受信します。 vm01 から受信したパケットは pbf で受信した後、CoS の値に応じて pbf_cos7 か pbf_default へパケットを転送し、 pbf_cos7 、 pbf_default から送信します。 これを実現するためには、以下の要件を満たすように設定する必要があります。 要件 1 : CoS に基づいた EVI への転送を優先するため、 PBF を行う EVI (EVI 4000) は EVPN で経路を受信させない 仮に EVI 4000 に対向 rt04 の経路が存在するとそちらが優先され、転送用 EVI が使用されなくなるためです。 要件 2 :転送用 EVI が 対向 MAC アドレスとポートの組み合わせを誤って学習しないように、EVPN 越しに受け取った BUM トラフィックをパケット転送用の EVI (EVI 14000、EVI 24000) へ転送させない 仮に EVI 4000 から EVI 14000 に BUM を転送してしまうと、EVI 14000 はその送信元 MAC アドレスをみて、転送先を EVPN 経路ではなく EVI 4000 に戻るポートへ指定するテーブルを作成してしまい、 EVI 4000 と EVI 14000 の間でループが発生するためです。 検証手順 以下の手順で検証します。 Underlay & メトリック の設定 EVI の設定 PXC の設定 PXC を用いた EVI 間の接続 LSP の定義 EVI と LSP の関連付け mac-filter の定義と EVI への適用 疎通確認 1. Underlay & メトリック の設定 省略します。 Underlay & VPN の設定は 第 13 回 を、メトリックの設定は 第 15 回 の記事をそれぞれ参照ください。 2. EVI の設定 PBF 用のインスタンス 1 つと、転送用のインスタンス 2 つ、計 3 つのインスタンスを立てます。 要件 1 を満たすために経路送受信を reject するポリシーを作成し、以下のように適用します。 PBF 用のインスタンス: vsi import 時に適用する(経路を受信しない) 転送用のインスタンス: vsi export 時に適用する(経路を送信しない) 経路送受信を拒否するためのポリシー [gl:/configure] A:user@rt01# info policy-options policy-statement "all-reject" entry 1 { action { action-type reject } } PBF 用 EVI [gl:/configure service vpls "pbf"] A:user@rt01# info admin-state enable service-id 4000 customer "cust-a" bgp 1 { route-distinguisher "10.255.0.1:4000" vsi-import ["all-reject"] route-target { export "target:65000:4000" import "target:65000:4000" } } bgp-evpn { evi 4000 mpls 1 { admin-state enable auto-bind-tunnel { resolution filter resolution-filter { sr-isis true } } } } sap 1/1/c4/1:4001 { admin-state enable } CoS が 0(デフォルト値)のパケット転送用 EVI [gl:/configure service vpls "pbf_default"] A:user@rt01# info admin-state enable service-id 14000 customer "cust-a" bgp 1 { route-distinguisher "10.255.0.1:14000" vsi-export ["all-reject"] route-target { import "target:65000:4000" } } bgp-evpn { evi 14000 mpls 1 { admin-state enable auto-bind-tunnel { resolution any } } } CoS が 7 のパケット転送用 EVI [gl:/configure service vpls "pbf_cos7"] A:user@rt01# info admin-state enable service-id 24000 customer "cust-a" bgp 1 { route-distinguisher "10.255.0.1:24000" vsi-export ["all-reject"] route-target { import "target:65000:4000" } } bgp-evpn { evi 24000 mpls 1 { admin-state enable auto-bind-tunnel { resolution any } } } 3. PXC の設定 EVI 同士を接続するために PXC を設定します。 PXC には物理ポートを消費するモードと消費しないモードがありますが、本記事の検証では後発で開発された後者を使用します。 以下のように pxc port を接続し、EVI 同士を接続します。 pxc-1.a: pbf_default <-> pxc-1.b: pbf pxc-2.a: pbf_cos7 <-> pxc-2.b: pbf PXC の詳しい仕様を知りたい方は こちら を参照ください。 card 1 { card-type iom-1 mda 1 { mda-type me6-100gb-qsfp28 xconnect { mac 1 { loopback 1 { bandwidth 100 } loopback 2 { bandwidth 100 } } } } } port-xc { pxc 1 { admin-state enable port-id 1/1/m1/1 } pxc 2 { admin-state enable port-id 1/1/m1/2 } } port 1/1/m1/1 { admin-state enable } port 1/1/m1/2 { admin-state enable } port pxc-1.a { admin-state enable ethernet { mode hybrid encap-type dot1q } } port pxc-1.b { admin-state enable ethernet { mode hybrid encap-type dot1q } } port pxc-2.a { admin-state enable ethernet { mode hybrid encap-type dot1q } } port pxc-2.b { admin-state enable ethernet { mode hybrid encap-type dot1q } } 設定した PXC port が Up していることを確認します。 A:user@rt01# /show port =============================================================================== Ports on Slot 1 =============================================================================== Port Admin Link Port Cfg Oper LAG/ Port Port Port C/QS/S/XFP/ Id State State MTU MTU Bndl Mode Encp Type MDIMDX ------------------------------------------------------------------------------- 1/1/c1 Up Link Up conn 100GBASE-LR4* 1/1/c1/1 Up Yes Up 9212 9212 - hybr dotq cgige 1/1/c2 Up Link Up conn 100GBASE-LR4* 1/1/c2/1 Up Yes Up 9212 9212 - hybr dotq cgige 1/1/c3 Up Link Up conn 100GBASE-LR4* 1/1/c3/1 Up Yes Up 1506 1506 - hybr dotq cgige 1/1/c4 Up Link Up conn 100GBASE-LR4* 1/1/c4/1 Up Yes Up 9212 9212 - hybr dotq cgige 1/1/c5 Up Link Up conn 100GBASE-LR4* 1/1/c5/1 Up No Down 9212 9212 - hybr dotq cgige 1/1/c6 Up Link Up conn 100GBASE-LR4* 1/1/c6/1 Up No Down 9212 9212 - hybr dotq cgige 1/1/m1/1 Up Link Up anchor 1/1/m1/2 Up Link Up anchor =============================================================================== Ports on Slot A =============================================================================== Port Admin Link Port Cfg Oper LAG/ Port Port Port C/QS/S/XFP/ Id State State MTU MTU Bndl Mode Encp Type MDIMDX ------------------------------------------------------------------------------- A/1 Up Yes Up 1514 1514 - netw null faste MDI A/3 Down No Down 1514 1514 - netw null faste A/4 Down No Down 1514 1514 - netw null faste =============================================================================== Ports on Port Cross Connect 1 =============================================================================== Port Admin Link Port Cfg Oper LAG/ Port Port Port C/QS/S/XFP/ Id State State MTU MTU Bndl Mode Encp Type MDIMDX ------------------------------------------------------------------------------- pxc-1.a Up Yes Up 9208 9208 - hybr dotq cgige pxc-1.b Up Yes Up 9208 9208 - hybr dotq cgige =============================================================================== Ports on Port Cross Connect 2 =============================================================================== Port Admin Link Port Cfg Oper LAG/ Port Port Port C/QS/S/XFP/ Id State State MTU MTU Bndl Mode Encp Type MDIMDX ------------------------------------------------------------------------------- pxc-2.a Up Yes Up 9208 9208 - hybr dotq cgige pxc-2.b Up Yes Up 9208 9208 - hybr dotq cgige =============================================================================== 4. PXC を用いた EVI 間の接続 PXC を Virtual Private LAN Service (VPLS) の SAP として設定することで EVI に PXC port を接続します。 要件 2 で述べた EVI pbf において EVPN 経由で受け取った BUM トラフィックを PXC へ流さないようにするため、split horizon group の設定を bgp-evpn と sap pxc-x.b:0 の階層へ追加します。 [gl:/configure] A:user@rt01# info service vpls "pbf" { bgp-evpn { split-horizon-group "SHG-pbf" } split-horizon-group "SHG-pbf" { } sap pxc-1.b:0 { admin-state enable split-horizon-group "SHG-pbf" } sap pxc-2.b:0 { admin-state enable split-horizon-group "SHG-pbf" } } vpls "pbf_cos7" { sap pxc-2.a:0 { admin-state enable } } vpls "pbf_default" { sap pxc-1.a:0 { admin-state enable } } 各 EVI で PXC が紐づいていることが確認できます。 [/] A:user@rt01# show service id 4000 sap =============================================================================== SAP(Summary), Service 4000 =============================================================================== PortId SvcId Ing. Ing. Egr. Egr. Adm Opr QoS Fltr QoS Fltr ------------------------------------------------------------------------------- pxc-1.b:0 4000 1 none 1 none Up Up pxc-2.b:0 4000 1 none 1 none Up Up 1/1/c4/1:4001 4000 1 mac 1 none Up Up ------------------------------------------------------------------------------- Number of SAPs : 3 ------------------------------------------------------------------------------- =============================================================================== [/] A:user@rt01# show service id 14000 sap =============================================================================== SAP(Summary), Service 14000 =============================================================================== PortId SvcId Ing. Ing. Egr. Egr. Adm Opr QoS Fltr QoS Fltr ------------------------------------------------------------------------------- pxc-1.a:0 14000 1 none 1 none Up Up ------------------------------------------------------------------------------- Number of SAPs : 1 ------------------------------------------------------------------------------- =============================================================================== [/] A:user@rt01# show service id 24000 sap =============================================================================== SAP(Summary), Service 24000 =============================================================================== PortId SvcId Ing. Ing. Egr. Egr. Adm Opr QoS Fltr QoS Fltr ------------------------------------------------------------------------------- pxc-2.a:0 24000 1 none 1 none Up Up ------------------------------------------------------------------------------- Number of SAPs : 1 ------------------------------------------------------------------------------- =============================================================================== 5. LSP の定義 転送用の各 EVI にて受信した経路に対し、設計に従った LSP を定義することで EVI へ関連付けを行います。 定義した LSP と EVI の関連付けには admin-tag 機能を用います。 Dynamic TE の LSP と admin-tag を定義します。 [gl:/configure routing-options] A:user@rt01# info admin-tags { admin-tag "tag-igp-metric" { } admin-tag "tag-te-metric" { } route-admin-tag-policy "RATP-igp" { include "tag-igp-metric" { } } route-admin-tag-policy "RATP-te" { include "tag-te-metric" { } } } [gl:/configure router "Base" mpls] A:user@rt01# info path "metric-te-path" { admin-state enable } lsp "dynamic-metric-IGP" { admin-state enable type p2p-sr-te to 10.255.0.4 vprn-auto-bind true path-computation-method local-cspf metric-type igp admin-tag "tag-igp-metric" { } primary "metric-te-path" { admin-state enable } } lsp "dynamic-metric-TE" { admin-state enable type p2p-sr-te to 10.255.0.4 vprn-auto-bind true path-computation-method local-cspf metric-type te admin-tag "tag-te-metric" { } primary "metric-te-path" { admin-state enable } } 6. EVI と LSP の関連付け EVI の経路 import 時に admin-tag を指定することで特定の LSP と紐づけます。 [gl:/configure policy-options] A:user@rt01# info community "target-4000" { member "target:65000:4000" { } } policy-statement "import-cos7" { entry 1 { from { community { name "target-4000" } } action { action-type accept admin-tag-policy "RATP-te" } } } policy-statement "import-default" { entry 1 { from { community { name "target-4000" } } action { action-type accept admin-tag-policy "RATP-igp" } } } [gl:/configure service vpls "pbf_default"] A:user@rt01# info bgp 1 { vsi-import ["import-default"] } [gl:/configure service vpls "pbf_cos7"] A:user@rt01# info bgp 1 { vsi-import ["import-cos7"] } 7. mac-filter の定義と EVI への適用 CoS=7 のパケットのみを pxc-2.b:0 、その他を pxc-1.b:0 へ転送する filter を定義し、 pbf の SAP interface へ適用します。 [gl:/configure filter mac-filter "for_vpls-4000"] A:user@rt01# info filter-id 4001 entry 1 { match { dot1p { priority 7 mask 7 } } action { forward { sap { vpls "pbf" sap-id pxc-2.b:0 } } } } entry 10 { action { forward { sap { vpls "pbf" sap-id pxc-1.b:0 } } } } 作成した filter が適用されている事と、パケットの転送先が指定した LSP になっている事を確認します。 [/] A:user@rt01# show filter mac "for_vpls-4000" =============================================================================== Mac Filter =============================================================================== Filter Id : 4001 Applied : Yes Scope : Template Def. Action : Drop Entries : 2 Type : normal Description : (Not Specified) Filter Name : for_vpls-4000 ------------------------------------------------------------------------------- Filter Match Criteria : Mac ------------------------------------------------------------------------------- Entry : 1 FrameType : Ethernet Description : (Not Specified) Log Id : n/a Src Mac : Undefined Dest Mac : Undefined Dot1p : 7/7 Ethertype : Undefined DSAP : Undefined SSAP : Undefined Snap-pid : Undefined ESnap-oui-zero : Undefined Primary Action : Forward (SAP) Next Hop : pxc-2.b:0 Service Id : 4000 PBR Target Status : Up Secondary Action : None PBR Down Action : Drop (entry-default) Downloaded Action : Primary Dest. Stickiness : None Hold Remain : 0 Ing. Matches : 22829 pkts (2417234 bytes) Egr. Matches : 0 pkts Entry : 10 FrameType : Ethernet Description : (Not Specified) Log Id : n/a Src Mac : Undefined Dest Mac : Undefined Dot1p : Undefined Ethertype : Undefined DSAP : Undefined SSAP : Undefined Snap-pid : Undefined ESnap-oui-zero : Undefined Primary Action : Forward (SAP) Next Hop : pxc-1.b:0 Service Id : 4000 PBR Target Status : Up Secondary Action : None PBR Down Action : Drop (entry-default) Downloaded Action : Primary Dest. Stickiness : None Hold Remain : 0 Ing. Matches : 30271 pkts (3202038 bytes) Egr. Matches : 0 pkts =============================================================================== 8. 疎通確認 CoS の値に応じてそれぞれ適切な経路が選択され TE が実現できているかどうかは、 monitor port コマンドを用いて各ポートから送信されているパケットの増減を確認することで確認します。 CoS=0 のとき (default) vm01で ping を打ちながら monitor port を実行します。 1/1/c1/1 ポートから送信されるパケットが増えているかを確認することで、上図の青色の経路を通っていることを確認します。 user@vm01:~$ ping 192.168.29.2 -i 0.2 output packet の数を確認すると 1/1/c1/1 ポートから送信されるパケットが多いことから、 IGP Metric が最小となるような経路を通っていることが確認できます。 [/] A:user@rt01# monitor port 1/1/c1/1 interval 5 =============================================================================== Monitor statistics for Port 1/1/c1/1 =============================================================================== Input Output ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- At time t = 0 sec (Base Statistics) ------------------------------------------------------------------------------- Octets 1429860905 564867974 Packets 4502761 4898538 Errors 0 0 ------------------------------------------------------------------------------- At time t = 5 sec (Mode: Delta) ------------------------------------------------------------------------------- Octets 3100 3100 Packets 25 25 Errors 0 0 ------------------------------------------------------------------------------- At time t = 10 sec (Mode: Delta) ------------------------------------------------------------------------------- Octets 4783 3310 Packets 28 28 Errors 0 0 ... [/] A:user@rt01# monitor port 1/1/c2/1 interval 5 =============================================================================== Monitor statistics for Port 1/1/c2/1 =============================================================================== Input Output ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- At time t = 0 sec (Base Statistics) ------------------------------------------------------------------------------- Octets 141881464 513956143 Packets 1605842 4452348 Errors 0 0 ------------------------------------------------------------------------------- At time t = 5 sec (Mode: Delta) ------------------------------------------------------------------------------- Octets 263 120 Packets 3 1 Errors 0 0 ------------------------------------------------------------------------------- At time t = 10 sec (Mode: Delta) ------------------------------------------------------------------------------- Octets 0 200 Packets 0 2 Errors 0 0 ... CoS=7 のとき (default) CoS を変更後、vm01で ping を打ちながら monitor port を実行します。 1/1/c2/1 ポートから送信されるパケットが増えているかを確認することで、上図の青色の経路を通っていることを確認します。 user@vm01:~$ sudo ip l set dev ens194.4001 type vlan egress-qos-map 0:7 user@vm01:~$ ping 192.168.29.2 -i 0.2 output packet の数を確認すると 1/1/c2/1 ポートから送信されるパケットが多いことから、 TE Metric が最小となるような経路を通っていることが確認できます。 [/] A:user@rt01# monitor port 1/1/c1/1 interval 5 =============================================================================== Monitor statistics for Port 1/1/c1/1 =============================================================================== Input Output ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- At time t = 0 sec (Base Statistics) ------------------------------------------------------------------------------- Octets 1430139849 565046533 Packets 4504418 4900028 Errors 0 0 ------------------------------------------------------------------------------- At time t = 5 sec (Mode: Delta) ------------------------------------------------------------------------------- Octets 4699 144 Packets 27 2 Errors 0 0 ------------------------------------------------------------------------------- At time t = 10 sec (Mode: Delta) ------------------------------------------------------------------------------- Octets 3250 197 Packets 27 2 Errors 0 0 ... A:user@rt01# monitor port 1/1/c2/1 interval 5 =============================================================================== Monitor statistics for Port 1/1/c2/1 =============================================================================== Input Output ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- At time t = 0 sec (Base Statistics) ------------------------------------------------------------------------------- Octets 141893876 513997471 Packets 1605984 4452709 Errors 0 0 ------------------------------------------------------------------------------- At time t = 5 sec (Mode: Delta) ------------------------------------------------------------------------------- Octets 79 3300 Packets 1 27 Errors 0 0 ------------------------------------------------------------------------------- At time t = 10 sec (Mode: Delta) ------------------------------------------------------------------------------- Octets 0 3186 Packets 0 26 Errors 0 0 ... 以上で、想定した L2VPN Per-Flow Steering が実現できていることが確認できました。 まとめ IOS XR 、Junos と SR OS の Multi-vendor 環境における L3VPN、L2VPN 上での Per-Flow Steering の検証結果を紹介しました。 特に L2 での Per-Flow 実現は SR OS に専用の機能が無いため、機能を組み合わせて実現する必要がありました。 次回の記事では TI-LFA を利用した 高速迂回(Fast Reroute)について紹介予定です。 (2023/10/30 追記) 公開しました: [Multi-AS Segment Routing 検証連載 #18] TI-LFA を用いた障害時の高速迂回 と Microloop の回避 (using SR OS with IOS XR / Junos)
アバター