TECH PLAY

NTTドコモビジネス

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

602

はじめに こんにちは、イノベーションセンターの内藤です。NTTコミュニケーションズ株式会社 (以下、NTT Com) は、世界最大級のネットワーク展示会である 「Interop Tokyo 2023(会場:幕張メッセ、会期:2023年6月14日〜16日)」 において構築されるShowNet 1 に対しコントリビューターとしてローカル5G(以下、L5G)システムを提供し、実機運用することに成功しました。 昨年 2 に引き続き、今年はより高度なマルチベンダーでの5Gコア/RAN端末の相互接続やネットワークスライシングにチャレンジ。実験試験局の免許も取得して無線区間をOTA 3 とし、End-to-Endでアプリケーションのトラフィック流通を実現しました。 本記事ではその構成やスライシング手法、技術的なチャレンジについて解説します。 マルチベンダーによるEnd to Endスライシング 私たちは、ユーザーの要求に応じてネットワークやエッジコンピューティング環境といったリソースを柔軟に提供することを目標としています。 それを達成するには、ユーザーやアプリケーション単位での細かい粒度で品質の異なるNW経路を提供する、ネットワークスライシング技術が必要になります。 ネットワークスライシングの説明は昨年の記事が参考になります。今回は詳細記載を省略します。 NTT ComではShowNetにて、昨年に引き続きマルチベンダーによる基地局(gNB)から5Gコアの先のData Network (N6) を含めたEnd to Endスライシングの実現にチャレンジしました。 下図が構成となります。順番に解説していきます。 スライシングに関する昨年からのアップデートは以下3点です。 1.マルチベンダーでのスライシングにチャレンジ Capgemini Engineering ViNGCは、Kubernetes/OpenShiftで動作する5GCであり、各Functionは1つ以上のPodとして構成されます。 C/U分離が可能であることから、UPFはKaloom UPFを使用します。Kaloom UPFはOpenShift上で動作するUPFであり、Edgecore Networks社のホワイトボックススイッチ上で動作します。 LeafとUPFからなる1つのクラスタで、複数の仮想UPFを動作させることが可能です。 ネットワークスライシングの手法は多数あり、昨年はS-NSSAIを用いた手法を実施しました。今回はマルチベンダーで相互接続をする観点から、インターオペラビリティの成熟度も探りつつ、仮想UPFを3台構成し、それぞれのN6に異なるDNN 4 を設定する方式を採用しました。DNNに紐ついたバックボーンネットワークは異なる品質設定がされており、UEではDNNを切り替えることで通過するネットワークの変更ができます。これをDNNスライスと定義しました。 2.5GCの設置場所としてMECを想定し、実サービスであるSDPF Edge 5 と連携 SDPF Edgeは任意のVM+アプリケーションをクラウド上の管理コンポーネントからZTPし、ユーザ拠点で起動できるサービスです。ViNGCの起動にはリソースが多く必要だったため、まだ商用ラインナップにはない高リソースMECをカスタマイズして作成し、Kubernetesを起動。5GCを構築しました。 3.自社開発のオーケストレーターであるQmonusを利用し設定自動化を実現 ViNGCに対して加入者データの投入やスライス作成/削除は、単体のAPI実行で完結するものではなく、複数のAPIを直列に、かつ5GC/UPF/バックボーン機器それぞれに対して操作が必要​となります。 NTT Com内製のマイクロサービス開発フレームワーク「Qmonus SDK」のトランザクション機能を用いて、システムを跨いだオーケストレーションスコープの一貫性を担保できます。トランザクションの状態と進行状況はフレームワークで管理されており、処理失敗時にはトランザクションの進捗状況に合わせて、各システムに対して最適な打ち消し処理を実行できます。 Qmonusを用いることでAPIによるデータ投入を実現しました。 なお、今回私たちはAWS Outposts ServerについてもShowNetにコントリビュートし、その上にNTTドコモの5GCを構築しました。この5GCについてもQmonusとの連携を実現しています。NTTドコモのコントリビューション部分については、NTTドコモのENGINEERING BLOG 6 をご覧ください。 実験試験局の運用 昨年のShowNetではUE – RU – CU/DUの無線区間はエミュレートしましたが、今年は免許申請を行い、電波を発波する実験試験局を運用しました。NTT Comには無線従事者免許を保有しているエンジニアが多数在籍しています。遵法し適切に電波を使用できるよう、輪番を組んで運用しました。 gNB装置は昨今登場した「RU/CU/DU一体型」を特徴とするものを採用。Airspan社のAirSpeed 1900を幕張メッセのhall6に設置しました。Interop準備期間中、遮蔽物が少なく、同時接続するUEの数も少ない好条件では期待以上の速度(DL790Mbps)が観測できたことも。 IoT Connect Gatewayとの接続 私たちはL5Gとその他サービスの組み合わせによる新しいユースケースの発掘にも日々挑戦しており、今回はN6に接続するサービスとしてIoT Connect Gateway(以下、ICGW)を選択しました。 ICGWは、IoTデバイスの通信をクラウド側でプロトコル変換(暗号化など)することでお客様の開発・運用コストを削減できるサービスです。 今回はCO2センサを会場に複数設置し、L5Gを介しICGWに接続。データの可視化を行いました。 ICGWはNTT Comが提供するSDP 7 サービスのひとつであり、今後も他のSDPサービスとL5Gとの連携を狙っていきます。 おわりに この記事ではInterop Tokyo 2023(会場:幕張メッセ、会期:2023年6月14日〜16日)において構築されたShowNetについて、L5GのマルチベンダーによるEnd to Endスライシングなどを紹介をしました。今後は、今回のフィールドテストの成果を踏まえより多種多様なユースケースに対応できるネットワークスライシングの方式や、UE/RU/CU/DU/5GC/UPE構成のベストプラクティスとなる提供方法を探り、NTT Comのネットワークサービスへの応用について検討していきます。 なお、私たちのチームはインターン 8 も募集しています。一緒にL5Gの高度化に取り組んでくれる仲間を募集しています。 ぜひご検討ください。 ShowNet: Interop Tokyoにおいて、様々な機器・サービスの相互接続性検証を実施するとともに、近未来のサービスアーキテクチャを実際に動かしている形で見ることができる世界最大規模のライブネットワークデモンストレーションです。Interop Tokyoへの出展社がインターネットへの接続性を利用して製品の動態展示のほか、来場者のインターネットへのアクセスとしても利用されるネットワーク。 https://www.interop.jp/2023/shownet/ ↩ 昨年: ShowNet2022でも我々はネットワークスライシングの取り組みを実施しました。 https://engineers.ntt.com/entry/2022/06/15/090043 ↩ OTA: Over The Airの略。データの送受信を無線通信で行う技術をさします。 ↩ DNN: Data Network Nameの略。5Gにおいて、UPFのN6以降のネットワークをさします。 ↩ SDPF Edge: NTT Comが提供中のMEC(Multi-access Edge Computing)サービスです。 https://www.ntt.com/business/sdpf/service/edge.html ↩ ENGINEERING BLOG: https://nttdocomo-developers.jp/ ↩ SDP: データ利活用に必要な機能をワンストップで提供するNTT Comのアーキテクチャです。 https://www.ntt.com/business/sdpf/about.html ↩ インターン: 「先端研究開発(ネットワーク・デバイス)」分野の「ローカル5G無線(RAN), コア(5GC), トランスポートネットワーク(TN)の技術検証」が私のチームです。 https://information.nttdocomo-fresh.jp/event/on-site-internship/ ↩
アバター
目次 はじめに 開発背景 機能紹介 実際に使ってみた ストレージ転送機能のセットアップ SSH一括実行機能のセットアップ SSH一括実行機能の動作確認 おわりに はじめに こんにちは、5G & IoT部/IoTサービス部門のIoT Connect Gateway (ICGW)サービス開発チームの岩田です。 我々のチームでは日頃からインキュベーション開発というものに取り組んでおります。 インキュベーション開発では、近い将来の需要や新規分野への展開を見据えた機能を実証実験的に開発し、実際に需要が上がった際に迅速に機能を商用展開することを目的としています。 本記事では、インキュベーション開発として実装されて機能の1つである SSH一括実行機能 を紹介します。 開発背景 ICGWでは2022年12月から リモートアクセス という機能の提供を開始しました。 リモートアクセス機能では、お客様の必要な時にセキュアなリモートアクセス用の経路をオンデマンドで提供します。 この機能を活用することで、お客様拠点や外出先から遠隔地の各デバイスにアクセスし、遠隔監視や設定変更などのメンテナンスを行うことができます。 ICGWを利用可能なSIMを装着したデバイスであれば、デバイス側に特別な設定をする必要はなく、SSH接続を含めた任意のデバイスポートにアクセスできます。 また、リモートアクセス経路を作成する際に、デバイスへアクセス可能なソースIPを制限できるのでセキュアな経路を確保できます。 利用料金に関しては、使用した月にのみ料金が発生する課金方式をとっているため、必要な時にだけ最低限のコストでご利用いただけます。 図1 リモートアクセス機能の概要図 リモートアクセス機能をさらに活用していく上で「あるデバイスでの設定変更やアップデートを他のデバイスにも同様に行いたい」というケースが想定できます。 例えば、各デバイスに蓄積されたログファイルを定期的に一括で取得したい・該当のモジュールやファームを一括で更新したい・最新の設定ファイルを各デバイスに配布したい、といったケースが考えられます。 デバイスの数が少ないうちは運用者が逐一接続して作業できますが、デバイスが増えるほど困難を伴います。 この課題を解決するために開発された機能がSSH一括実行機能になります。 機能紹介 図2 SSH一括実行機能の概要図 SSH一括実行機能は「特定のデバイスグループに対してSSH接続し、ユーザ定義したスクリプトを実行する機能」です。 利用の流れは以下のようになります。 SSHグループの設定 手動でSSH一括実行をトリガー リザルト画面で結果を確認 ステップ1では、一括実行対象のSIMや実行するスクリプトを登録します。 SSH接続では鍵認証方式を利用するため、鍵情報やログインユーザなどのセットアップも行います。 ステップ3では、実際にデバイス上で実行されたコマンドの結果をリザルト画面から確認できます。ログテキストやexitコードといった詳細情報を提供しています。 実際に使ってみた 今回は「各デバイスに蓄積されたファイルを一括でAmazon S3に送信する」というケースを例にとって、SSH一括実行機能を紹介します。 図3 各デバイスのファイルを一括でAmazon S3に送信するユースケース ユーザがSSH一括実行機能をトリガー ICGWが各デバイスに対してSSH接続しコマンドを実行 デバイスからICGWに対してストレージ機能を使いファイルを送信 送信されたファイルをAmazon S3へ転送 図3のフローを実装することを目標にセットアップをしていきます。 デバイスとして、Aterm MR10LN とRaspberry Piをそれぞれ2セット用意しました。 Atermには IoT Connect Mobile Type S のSIMを挿入してあります。 図4 AtermとRaspberry Pi 図5 ICGWとデバイス間の接続構成 ストレージ転送機能のセットアップ まず、デバイスからAmazon S3へデータを送信するためのセットアップを行います(図3のステップ3-4)。 デバイスから直接Amazon S3へデータを送信することも可能ですが、今回はデータの暗号化やクラウド接続をサポートできるICGWの ストレージ転送機能 を利用します。 ストレージ転送機能の詳細に関しては 過去の記事 で取り扱っておりますので、そちらもご参照ください。 図6 ストレージ転送機能の設定画面 ストレージ転送機能の設定画面では、ICGW側のデータの受け口である エントリーポイント やデータの転送先である 宛先設定 、付与するデータを設定する必要があります。 今回はICGWのポータルから図6のように設定しました。 ポイントとしては宛先設定のファイルパスを $YYYY-$MM-$DD/$imsi/log.txt とすることで、データ送信時にICGW側で自動的に日時やSIMの固有識別子であるIMSI(International Mobile Subscriber Identity)を埋め込んでくれるようにしています。 そのため、各デバイスから同一のコマンドでファイルを送信しても、Amazon S3上でファイル名が重複しないように転送できます。 デバイスからのファイル送信コマンドとして、以下のコマンドを実行します(ICGWのエントリーポイントは こちら をご参照ください)。 curl -v -X PUT --data-binary @./Documents/test/ssh/sample_log.txt http://***:8081/S3_ssh SSH一括実行機能のセットアップ 次に、ICGWからデバイスへSSH接続するためのセットアップを行います(図3のステップ2)。 以下のコマンドで、 raspberry-pi-zero という名前の公開鍵認証用キーペアの作成し、その公開鍵をRaspberry Piへ転送します。 # キーペア作成 ssh-keygen -t rsa -b 4096 -f raspberry-pi-zero # 公開鍵転送 ssh-copy-id -i ~/.ssh/raspberry-pi-zero.pub user@raspberrypi.local 鍵の準備ができたのちに、ICGWのポータル上でSSH一括実行機能の設定をします。 Secret Key には先ほど作成したキーペアの秘密鍵を、 Command にはストレージ転送機能のセットアップで使用したコマンドをそれぞれ登録します。 今回は2デバイスを対象に実行するので、 SIMs には該当の2SIMを登録しています。 図7 SSH一括実行機能の設定画面 SSH一括実行機能の動作確認 一通りの設定を終えたので、実際にSSH一括実行機能の動作を確認します。 図8 SSH一括実行機能のトップ画面 図8の EXECUTE から手動でトリガーします。 実行が終わったのちに、ICGWのポータル上から各デバイスに対するコマンドの実行ログを確認できます。 図9では Status や Exit Code が表示されていますが、さらに各IMSIを選択することで図10のような詳細な実行ログテキストも確認できます。 図9 SSH一括実行機能のログ画面 図10 SSH一括実行機能のログ詳細画面 ポータル上のログからSSH一括実行機能が問題なく動作していることがわかったので、Amazon S3にファイルが送信されているか確認します。 図11のように日付のフォルダが作成され、その中にIMSI毎のフォルダも作成されました。図12からIMSIのフォルダにはデバイスから送信されたファイルが格納されていることも確認できました。 図11 デバイスからAmason S3へファイルを送信した結果1 図12 デバイスからAmason S3へファイルを送信した結果2 以上で、SSH一括実行機能を活用した「各デバイスに蓄積されたファイルをAmazon S3に一括で送信する」ケースの実装および動作確認を行うことができました。 おわりに 今回はインキュベーション開発物である、SSH一括実行機能を紹介しました。 まだインキュベーション段階ということもあり、本機能は必要最低限のものとなっております。 さらなる機能追加として、定期的に実行できるようなスケジュール機能、デバイスに応じたコマンドを実行できるようなプレースホルダ機能などを検討しております。 皆様の反響次第では近い将来に商用展開できるかも(?)しれないので、ぜひお声をいただけると嬉しいです! 本記事を通して、ICGWに興味を持っていただけたら幸いです! ICGWに関するお問い合わせ先 IoT Connect Gatewayサービス詳細 ICGWに関するお問い合わせ先 トライアル、サービス導入に関するお問い合わせ 資料請求・お問い合わせフォーム 開発チームへのお問い合わせ(お手数ですが@を半角文字に置き換えてください) メールを送る
アバター
はじめに 当ポストは、低遅延配信の技術であるWHIP(WebRTC-HTTP Ingestion Protocol)を自分で動かしてみたい方へ向けた記事となります。 WebRTCを利用した配信映像の集信プロトコルであるWHIPは現在Internet-Draftではありますが、配信技術者やWebRTC技術者の注目を得て、実際に動かせる環境が整ってきました。 当ポストでは、WHIPが動く環境を作り、自前のWebRTCスタックをWHIPで使う簡単な方法を紹介します。 とはいえInternet-Draftということもあり、「WebRTCなら聞いたことあるけど、WHIPって何?配信とWebRTCが関係あるの?」という方も多いと思いますので、最初はWHIPの紹介から入りたいと思います。 WHIPとは WHIP は、WebRTCを利用したインジェストのためのHTTPSベースのシグナリングプロトコルです。 インジェストというのは、放送機器からメディアサーバーに映像音声を送信することを言います。 人によっては"アップストリーム"、"打ち上げ"などの方が馴染み深い言葉かもしれません。 つまりWHIPは、誤解を恐れずにいうとRTMPのような配信のアップストリームにWebRTCを使おうといった試みです。 さらにWebRTCは、リアルタイム性を重視した双方向コミュニケーションの技術スタックです。 Webで使えるビデオ通話の技術ですね。 WebRTCの実体は大別して以下の特徴を持ったプロトコルセットです。 リアルタイムメディア伝送のためのワイヤプロトコルのセット メディアストリームを設定・制御するための記述プロトコル(SDP) これらにより、どうやって端末間の通信を確立するか?どうやってパケットをほどき、どうやって映像・音声として解釈するか?といった課題を柔軟に解決しています。 最近はこのリアルタイム性やセッションの自由度などから、大規模配信のメカニズムとしてWebRTCの利用が人気を集めており、いくつかの企業がWebRTCベースのメディア配信ネットワークを展開しています。 さて、WebRTCをアップストリームに利用しようとしたとき、ひとつの課題が浮き上がります。 WebRTCを利用するサービスでは、それぞれが独自にシグナリングプロトコルを開発する必要があるということです。 セッションに必要な情報は用途によって異なるという考え方のため、WebRTCではセッション確立までのプロトコルが規定されていないことが理由です。 ただし放送の場合はメディアを送るためにエンコーダと呼ばれる専用の機器を用いることが多いため、WebRTCを利用したい場合、サービス毎に専用機器へシグナリングプロトコルを実装する必要があります。 そういった背景で立ち上がったのがIETFのWISH(WebRTC Ingest Signaling over HTTPS) WGであり、現在は放送機器からWebRTCメディアサーバーへのセッションを確立するためのシグナリングプロトコルである WebRTC-HTTP ingestion protocol (WHIP) がInternet-Draftとして提案されています。 このWHIPというプロトコルで、どういった順番でHTTPリクエストを出して、どういった内容のHTTPレスポンスを返してね、といったことが決められている訳です。 WHIPを体験してみる WHIPを体験してみるという場合でも、WHIPをこれから実装してみるという場合でも、まずは今実際に動作するソフトウェアのことを知っておくと役に立ちます。 簡単に、WHIPクライアントおよびWHIPサーバーの紹介と動かし方の解説をしたいと思います。 これ以降、配信映像の視聴方法としてブラウザは最新版のGoogle Chromeを推奨します。 simple-whip-server (WHIP server) 手軽に試せるWHIPサーバーは、janus-gatewayを用いた meetecho/simple-whip-server があります。 これはWHIPサーバーのエンドポイントになりつつ、janus-gatewayのAPIを操作してビデオルーム上にWHIPクライアントのメディアを流してくれます。 simple-whip-serverの動作条件としてHTTP, WebSocketを有効にしたjanus-gatewayが必要なので、まずはそのセットアップから解説します。 janus-gatewayのセットアップ WebSocketを有効にしたjanus-gatewayをビルドします。 まず janus-gatewayの依存解決 を参考に、依存環境を整えてください。 今回はREST APIとWebSocket APIを利用するので、最低限必要なのは当該ページのリストの内以下のものになります。 Jansson libconfig libnice OpenSSL libsrtp libmicrohttpd libwebsockets cmake 環境が揃ったら、リポジトリをcloneして autogen.sh を実行します。 $ git clone --depth 1 https://github.com/meetecho/janus-gateway $ cd janus-gateway $ sh autogen.sh すると configure という実行可能なスクリプトが生成されるので、これを実行します。 ここでjanusの関連ファイルのビルド先を --prefix で指定できます。 デフォルトが /opt/janus 以下なので、適当に設定します。 また、 configure を実行したら Makefile が生成され、 make できるようになるので実行します。 $ ./configure --prefix=/usr/local/janus --enable-websockets $ make ここで、 macOSを利用している方のみ 確認していただく内容があります。 src/transports/libjanus_websockets.la というファイルの中身を読んで、 dlname の値を確認します。 # The name that we can dlopen(3). dlname='libjanus_websockets.1.dylib' 上記のように、 libjanus_websockets.<version>.dylib のフォーマットで書き込まれていると思います。 この version の数字を覚えておいて、次は src/janus.c のプリプロセッサを確認します。 以下のようにライブラリの拡張子を指定している場所があります。 #ifdef __MACH__ #define SHLIB_EXT "0.dylib" #else この 0.dylib を先ほど確認した version の数字となるように編集してください。 例えば、 libtool 2.4.7 な私のmacOS環境で生成したファイルは libjanus_websockets.1.dylib となっていたので、以下のようにしておきます。 #ifdef __MACH__ #define SHLIB_EXT "1.dylib" #else 準備ができたら make install と、janus関連の設定ファイルを有効化するために make configs を実行します。 $ make install && make configs このあとクライアント側で映像コーデックにVP8とH.264を利用したいので、設定ファイルを一部修正します。 <prefix>/etc/janus/janus.plugin.videoroom.jcfg を編集し、 room-1234 の中の videocodec の値を vp8,h264 に変更してください。 room-1234: { description = "Demo Room" secret = "adminpwd" publishers = 6 bitrate = 128000 fir_freq = 10 #audiocodec = "opus" videocodec = "vp8,h264" # もともとは"vp8"のみで、コメントアウトされている record = false #rec_dir = "/path/to/recordings-folder" } この状態でjanusを実行して、最終的に以下のようなログが出れば成功です。 $ <prefix>/bin/janus (省略) Loading transport plugin 'libjanus_http.1.dylib'... HTTP transport timer started HTTP webserver started (port 8088, /janus path listener)... JANUS REST (HTTP/HTTPS) transport plugin initialized! このままjanusを実行させておいてください。以降の試験で利用します。 ついでに、別のプロセスでjanusのビデオルームをブラウザで開けるように、janus-gatewayの html ディレクトリ以下にあるデモHTMLをホストしてください。 $ cd html $ python -m http.server 下記URLが開ければ準備完了です。 http://localhost:8000/videoroomtest.html simple-whip-serverのセットアップ 次にWHIPのサーバーとなるsimple-whip-serverを用意します。 Node.jsが必要になります。 $ git clone --depth 1 https://github.com/meetecho/simple-whip-server $ cd simple-whip-server $ npm i これで準備ができました。 REST API, WebSocketが有効なjanus-gatewayのプロセスが立ち上がっていれば、 npm run start を実行すると以下のようなログが出力され、simple-whip-serverが実行されます。 $ npm run start > janus-whip-server@0.0.1 start > DEBUG=whip:*,-whip:debug,janus:*,-janus:debug,-janus:vdebug node src/server.js [1. Janus] Connecting to Janus: { address: 'ws://127.0.0.1:8188' } janus:info Connecting to ws://127.0.0.1:8188 +0ms janus:info Janus WebSocket Client Connected +13ms janus:info Janus session ID is 1549944700292951 +3ms janus:info Janus instance version: 1.1.3 (multistream) +2ms whip:info Connected to Janus: ws://127.0.0.1:8188 +0ms [2. WHIP REST API] WHIP REST API listening on *:7080 WHIP server prototype started! [ 'Janus OK', 'WHIP REST API OK' ] これでWHIPのサーバーサイドは準備できました。 ここまでで、以下の3つのアプリケーションが動作していることを前提として進めます。 janus-gateway janus-gateway/html の静的サーバー simple-whip-server 次に、janusのビデオ会議の部屋を作りつつ、WHIPクライアントが配信するためのリソースを作っておきましょう。 $ curl -H 'Content-Type: application/json' -d '{"id": "abc123", "room": 1234}' http://localhost:7080/whip/create simple-whip-serverが動いているときに http://localhost:7080 を開くことでWHIP配信先の管理ができるのですが、ここで Endpoint ID=abc123 であり VideoRoom=1234 であるエンドポイントが追加されていることを確認できます。 この状態で http://localhost:8000/videoroomtest.html を開いて、Startボタンを押し、ルーム名に 1234 を入力して Join the room ボタンを押すことで、このあとWHIPクライアントから配信した映像を視聴する画面に移ることができます。 WHIPサーバーを立て、WHIPクライアントが配信した映像を視聴する準備が整ったので、以降は三種類のWHIPクライアントを紹介します。 simple-whip-client (WHIP client) WHIPのクライアント、つまりインジェストを行うソフトウェアとして最初に紹介するのは、simple-whip-serverと対になる meetecho/simple-whip-client です。 かなりシンプルな実装であり、自作のWHIPサーバーの細かい検証をしたい場合では一番扱いやすいと思います。 依存環境として、以下が必要になります。 pkg-config GLib libsoup~=2.4 GStreamer>=1.16 gst-plugins-base gst-plugins-bad libsoup~=2.4 について、今libsoupを入れようとすると3系が入るので、2系を明示的にインストールするよう注意してください。 また、3系がシステムデフォルトになっている場合、ビルド時にpkg-configの参照先が2系になるように注意してください。 例えば私の環境では、ビルド時の環境変数 PKG_CONFIG_PATH に /usr/local/libsoup/2.74.2/lib/pkgconfig を追加しました。 simple-whip-clientのセットアップは以下です。 $ git clone --depth 1 https://github.com/meetecho/simple-whip-client $ cd simple-whip-client $ make make だけで whip-client というバイナリが作成されます。 実行する際は、 -A および -V オプションで配信する音声映像としてGStreamerのパイプラインを入力できます。 GST_DEBUG =WARNING ./whip-client -u " http://localhost:7080/whip/endpoint/abc123 " \ -A " audiotestsrc ! audioconvert ! audioresample ! queue ! opusenc ! rtpopuspay pt=100 ssrc=1 ! queue ! application/x-rtp,media=audio,encoding-name=OPUS,payload=100 " \ -V " videotestsrc ! videoconvert ! queue ! vp8enc deadline=1 ! rtpvp8pay pt=96 ssrc=2 ! queue ! application/x-rtp,media=video,encoding-name=VP8,payload=96 " 起動オプションも豊富で、WHIPのInternet-Draftにあるoptionalな実装を無効にしたりできます。 起動オプションはコマンドライン引数なしの実行か、 -h オプションをつけて実行することで確認できます。 GStreamer (WHIP client) ソフトウェアの音声/映像処理によく使われるGStreamerでも、 バージョン1.22 からWHIPがサポートされました。 利用するには、 gst-plugins-rs が必要です。 ここで追加された whipsink を利用すれば、以下のようなパイプラインでgstreamerから映像配信ができます。 gst-launch-1. 0 whipsink name =whip whip-endpoint =" http://localhost:7080/whip/endpoint/abc123 " \ audiotestsrc ! audioconvert ! opusenc ! rtpopuspay pt = 100 ! queue ! " application/x-rtp,media=audio,encoding-name=OPUS,payload=100 " ! whip. \ videotestsrc ! videoconvert ! queue ! vp8enc deadline = 1 ! rtpvp8pay pt = 96 ! queue ! " application/x-rtp,media=video,encoding-name=VP8,payload=96 " ! whip. OBS Studio (WHIP client) OBS Studioでは現在PRのDraftという状況ですが、 Sean-Der/obs-studio でWHIPを使うプラグインが開発されています。 PRは #7926 で、バイナリが欲しい場合は自分でビルドする他、OBS StudioではGitHub Actionsでのビルドが行われているので自身の環境にあったバイナリをArtifactからダウンロードすることもできます。 下記ページの最新のビルドから、Artifactsにアップロードされているファイルをダウンロードできます。 macos-x86_64 のように対応するOS・CPUアーキテクチャがファイル名に記されているので、自分の環境にあったパッケージをダウンロードしてください。 Actions・obsproject/obs-studio 自分でビルドするか、Actionsからダウンロード・インストールしたOBS Studioを利用すると、配信先の設定の選択肢に WHIP があります。 これを選択し、サーバーに http://localhost:7080/whip/endpoint/abc123 を入れることでOBS StudioからWHIPでの配信ができます。 おわりに 当ポストではWHIPのサーバーとしてsimple-whip-serverを、クライアントとしてsimple-whip-client・GStreamer 1.22・OBS Studioを紹介しました。 この記事がWHIPへの興味に繋がったら幸いです。 最後に宣伝をさせてください。 NTT Comには Smart vLive というサービスがあり、実はWebRTCを利用した配信をしています。 インターネット配信におけるWebRTCのリアルタイム性に注目しており、インジェスト側ではなくメディアを配信する側でWebRTCを利用しています。 本ポストでは「なんで配信をWebRTCでしたいの?」という論点を取り上げませんでしたが、Smart vLiveの事例を見ていただくとどんな利用シーンがあるのかイメージしやすいかと思います。 私が個人的に好きな事例はオークション/ライブコマースです。 リアルタイム性がそのまま価値につながる利用シーンの1つだと思います。 オンサイトとリモートの時間的優位性を極力減らしたときにどんな価値が生まれるのか? 未来の当たり前に向かって、技術開発はもちろん、こういった視点での価値創出も一緒に取り組んでくれる仲間をNTT Comでは募集しています。 WebRTC配信技術の開発をしている私のチームの募集ポストは こちら になります。
アバター
2023/5/19更新: 本記事のタイトルを「インターンシップ体験記 ~RedTeamでの攻撃技術検証業務~」から「インターンシップ生があるSaaSを用いた未知のC2脅威を実証してみた」に変更しました。内容に変更はありません。 イノベーションセンター RedTeamプロジェクトでは、2月に現場受け入れ型インターンシップを実施しました。 本記事は、本取り組みに参加していただいたインターンシップ生による寄稿となります。 インターンシップ生本人の希望により匿名での投稿とさせていただきます。 ※今回注目したあるSaaSに関する発展的な攻撃テクニック(本記事記載のC2への応用を含む)について、NTTコミュニケーションズ RedTeamとして国内外のカンファレンスへ投稿する予定です。 そのため、本記事では具体的なSaaS名の記載を控えます。 よろしくお願いいたします。 はじめに こんにちは、今回NTTコミュニケーションズのインターンシップに参加させて頂いた者です。 現在大学3年生で、大学ではWebセキュリティ、特にXSSの検知・防御に関する研究をしています。 2023年2月に2週間、RedTeamのメンバーとして業務体験をさせていただきました。 この記事ではインターンシップで行った取り組みについて紹介したいと思います。 概要 今回私は、RedTeamサービスを提供するにあたっての攻撃技術の開発・検証業務を行いました。 今回のインターンシップでは、初日と最終日の2日間を現地にて実施し、他の日程はリモートでの実施でした。 参加に至った経緯 私は元々サイバーセキュリティ分野に興味があり、これまでに脆弱性診断、PSIRT(脆弱性通報対応)、インシデントレスポンス、セキュリティツール開発などのインターンシップに参加しました。 インターンシップに参加する中で特にオフェンシブセキュリティに興味を持ち、RedTeamの業務体験ができるインターンシップを探したところこちらのインターンシップを見つけ、今回応募させていただきました。 また、これまでに参加したインターンシップでは長くとも4、5日という期間でしたが、今回は2週間の現場受け入れ型インターンシップということで、 より実務に近い部分で深くセキュリティエンジニアとしての業務を体験できるという点にも興味がありました。 取り組んだこと RedTeamの受け入れポストの応募要項としては、以下のような記述がありました。(2023年2月16日時点) 攻撃者視点を持ち企業のセキュリティ強化をミッションとするRedTeam ●業務内容 RedTeamサービスに関する攻撃技術の開発・検証を経験していただきます。 ■主な業務内容 MITRE ATT&CKをフレームワークとした攻撃技術の開発・検証 実際のサイバー攻撃と同様の技術を用いた、演習形式の企業セキュリティ対策評価 ■RedteamPJの業務一例 https://engineers.ntt.com/entry/2021/12/09/103248 ●応募要件 【必須】 RedTeamに興味があり、攻撃技術の深堀り、習得する意欲のある方 Pythonを用いたプログラミング経験がある方。 【推奨】 情報、通信、コンピュータ系、セキュリティ専攻学科の方 CTFなどのセキュリティコンテスト入賞経験 ●受け入れ会社 NTTコミュニケーションズ株式会社 ●勤務地 〒108-8118 東京都港区芝浦3-4-1 グランパークタワー 現場受け入れ型インターンシップコース | インターンシップ情報 | NTTドコモ新卒採用 主な業務内容の項にあるように、RedTeamサービスに関する攻撃技術の開発・検証に関する業務を行いました。 一般的にSaaSを悪用したC2の脅威について注意喚起されている事例がありますが、今回はまだ世の中に事例がない、 あるSaaSとそのエージェントの仕組みを悪用したC2のPoC(Proof of Concept: 概念実証)コードを開発しました。 初日に取り組むテーマを2つ提示していただき、興味のあるほうを選ぶという形で今回のテーマを進めていくことになりました。 今回開発したC2について まずC2とは、Command & Controlの略で、攻撃者が対象のホストへ侵入した後にホストと攻撃者の間で通信を確立し、 ネットワーク経由で外部からコマンドによる操作が可能な状態を維持する技術です。 今回注目したSaaSでは公式エージェントが提供されており、SaaS利用者はエージェントをインストールしたホストに対し、 SaaSを介して任意の処理を実行させることができます。 攻撃者がこのSaaSをC2として悪用できる可能性があり、今回はその脅威について調査し、実際に実現可能であるかを検証しました。 このSaaSをC2に利用する攻撃者側のメリットは、「通信先が攻撃者の用意したサーバーなどではなくSaaSとなるため悪性通信の判別が難しい」、 「公式から提供されているエージェント実行ファイルからコマンドを実行するため検知されにくい」という点です。 結果として、実際にC2の仕組みをCLIツールとして実装し、C2通信が可能であることを検証によって確かめられました。 また、Microsoft Defender for Endpoint(MDE)を有効にしたWindows環境下で検証したところ、通信の過程では検知がされないという結果となりました。 実装について 言語はPython3で、 Click というCLIツール用のライブラリを使用して開発しました。 コマンドとしては、以下のような構成で実装しました。 シェルコマンドによる命令実行 エージェントに付属のスクリプトエンジンによる命令実行 ファイルアップロード ファイルダウンロード ログ閲覧機能 ファイルアップロード/ダウンロードは、C2を通じて攻撃者と標的ホスト間でファイルを送受信するための機能です。 この機能は、目的達成に必要なファイルの転送やデータの持ち出しのために利用されることを想定しています。 命令の実行やファイルダウンロード/アップロードはSaaSやそのエージェントの機能を利用して実行し、出力結果をログ閲覧機能から確認するという形式になりました。 攻撃者が悪用する場合の実用性に関する検証 提供されているエージェントはシェルコマンドの実行及び、エージェント自身に付属のスクリプトエンジンで命令を実行できます。 付属のスクリプトエンジンを用いる場合、PowerShell等を使用しないため、 Antimalware Scan Interface (AMSI) による検知を回避できる可能性があります。 実際に悪性ファイルとしてInvoke-Mimikatz.ps1 1 を用い、付属のスクリプトエンジンでの命令実行により配布サイトからファイルダウンロードをする際にAMSIにより検知されるかについて検証しました。 結果的にファイルを保存する時点で検知されましたが、悪性ファイルを配信するURLへの通信という段階では検知がされないということが確かめられました。 企業の業務環境などでは認証プロキシが使用されていることがあります。 これらの環境を想定し、認証プロキシ下でC2通信ができるかについて検証しました。 結果として、現状提供されているエージェントはBASIC認証以外には対応しておらず、Kerberos認証やNTLM認証といった認証プロキシ下で通信させるには工夫が必要であるということがわかりました。 エージェントをサービスとして動作させるためには管理者権限が必要となります。 ユーザ権限下で永続化が可能であるかの検証として、永続化の代表的な手法である Boot or Logon Autostart Execution を検証しました。 Windowsでは、 C:\Users\[Username]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup などに配置したファイルが起動時に自動的に実行されるという機能があります。 ここに、VBScriptで記述したエージェントを起動させるためのスクリプトファイルを配置し、実際に起動時にエージェントが起動することを確かめられました。 PoC全体像 成果報告会について 最終日の前日にセキュリティプロジェクト内で、最終日にはNTTドコモグループ全体で、業務体験を通して得られた成果の報告会を行いました。 特に最終日の成果報告会では、セキュリティエンジニアのポストとして参加したインターン生以外に、ソフトウェアエンジニア、 ネットワークエンジニア、AIエンジニア、ビジネス企画、マーケティング、デザイナーなど様々な部署でのインターン生の成果発表を聞くことができました。 そのため、同年代の様々な就活生の取り組みや、他分野での業務イメージなどが掴めて良い経験となりました。 感想 自分の興味のある部分や実装していく中で思いついた提案などを元に、自由に開発や検証を進めさせていただいたため、インターンシップ期間を通して非常に多くのことが学べました。 業務を行うにあたって、学生が普段触れることのできないような企業向けセキュリティツールに触れることができたことも、とても貴重な経験となりました。 また、開発で詰まった際の相談や検証環境の準備についても親身にサポートして頂き、とてもやりやすい環境で業務に取り組むことができました。 また、期間中にチーム構成や過去のプロジェクトについての説明の時間を設けていただいたり、他部署合同のミーティングに参加させていただいたりしたため、 実際の企業における業務や組織の構成という点でも理解が深まりました。 おわりに 本記事では2022年度冬インターンシップの参加記として取り組みや感想について書かせていただきました。 親身にサポートをしてくださったトレーナー、RedTeam、NTTコミュニケーションズの皆様には大変お世話になりました。 2週間ありがとうございました。 認証情報窃取等の機能を持つMimikatzをメモリ上に読み込み、その機能を利用できるようにするPowerShellスクリプト。 ↩
アバター
はじめに こんにちは、イノベーションセンターの野山です。 今回、NTTコミュニケーションズの IoT Connect Gateway (以降ICGW)を用いて、IoT/Edgeのアーキテクチャ検証をしました。 NTTコミュニケーションズの以下メンバーとともに行った結果を共有させていただこうと思います。 角田 佳史 (PS本部 5G&IoT部/IoTサービス部門) 鈴ヶ嶺 聡哲 (イノベーションセンター テクノロジー部門) 野山 瑛哲 (イノベーションセンター テクノロジー部門) PoCの目的とモチベーション 今回のPoC(Proof of Concept)には、主に2つのモチベーションがあります。 1. IoTアーキテクチャのユースケース検証 このプロジェクトでは、IoTのアーキテクチャにおける具体的なユースケースを検証し、その適用可能性を技術的観点から検証することを目指しました。 これにより、読者の皆様にもこの技術を使用するイメージを明確に伝えられるよう、共有したいと考えています。 2. Azureの新技術の調査・検証 また、今回のPoCではMicrosoft Azureにおける最新技術の調査および検証も重要な目的としています。 特に以下の技術要素が注目されており、それらの適用範囲や効果を検証しました。 Azure Arc Enabled k8s この検証を通じて、これらの新技術の特徴や利点、そして適用上の注意点などを明らかにし、 皆様に有益な情報を提供できることを目指しています。 アーキテクチャ概要 先ほど述べた2つのモチベーションに基づいて、以下のようなPoCを実施しました。 このセクションでは、アーキテクチャの概要と主要な要素について説明します。 PoCイメージ 概要として、以下のような画像イメージを想定してPoCを構築した形となります。 ユースケースとしては、製造業などの各拠点に点在する拠点から、部品(ネジ/カメラ/カプセル/etc.....)の故障検知のアーキテクチャを作成しました。 流れとしては以下となります。 地方の拠点から定期的に撮影した画像を、中央拠点に定期的にアップロードする(大体30s~1mくらいの間隔を想定) アップロードされると同時にAWSのS3に画像をバックアップする 中央拠点にアップロードされた画像はその都度、故障推論を行いその様子をリアルタイムで可視化する 地方拠点でアップロードされる画像は15種類くらいを想定しました。とはいえ物理的に地方拠点で設置して、というのはなかなか難しいので サンプルイメージ を用いてランダムにアップロードすることで、イメージに近づけています。 Rawな画像と故障推論された画像が可視化された次の図です。アップロードの頻度によって動的に変わっていきます。 アーキテクチャ概要 このようなPoCのイメージを実現したアーキテクチャ概要が下のイメージとなります。 説明するにあたり、このアーキテクチャを3つの機能(Upload/Eventdriven/Inference)に分けて説明します。 Upload 地方の拠点から、画像をUploadする部分となります。 多くの拠点や様々な画像をアップロードし、S3にバックアップをするためにNTT Comの製品であるIoTConnectGateway(ICGW)を使用しています。 ポイントとしては3つあります。 UploadするDevice側の追加・削除はICGWを通して任意に変更でき、拡張性が高い ICGWのMirroring機能を用いて、Device側は意識することなく、S3へバックアップされる Device側の固有設定をICGWで設定できるため、統括管理しやすい (ConfigManager機能) 概要 要素技術の説明(ICGW) 上記でポイントとしているICGWにどんなサービス/機能があるのかご紹介します。 今回のPoCで使用しているICGWのサービスは以下の3つです。 ストレージ転送機能 ミラーリング機能(近日商用リリース予定) コンフィグマネージャー ストレージ転送機能とコンフィグマネージャーに関する機能説明や取り組みは、 過去連載でも紹介されているのでご興味がある方はご参照ください。 IoT Connect Gatewayを使ってみた 番外編 第2回 ~インターンシップでStorage転送機能を使って開発してみた~ 【IoT Connect Gateway】コンフィグマネージャーのご紹介 【IoT Connect Gateway】コンフィグマネージャーで設定ファイルを生成してみよう ストレージ転送機能 ストレージ転送機能は、クラウドストレージへデータ転送をする機能です。 ICGWの基本機能であるプロトコル変換機能をベースに、デバイス固有のIMSIやIMEIなどのメタデータを付与した上で転送先のクラウドストレージへデータを転送できます。 概要 今回のPoCでは、Amazon S3やオンプレミス上で構築されたMinioへカメラ画像を送信するために、ストレージ転送機能とミラーリング機能を使用しています。 現在、ストレージ転送機能は、AWS S3互換のストレージのみ対応していますが、OSSであるMinioはS3互換なのでICGWの転送先として設定できます。 ただ、 現在のストレージ転送機能単体では、設定されたエンドポイントへのデータ送信時に、複数のクラウドサービスへ同時にデータを転送はできません 。厳密に言えば、複数の転送設定を作成し、転送先毎にデータを送信する仕組みを、IoTデバイスへ実装すれば実現可能です。 とはいえ、転送設定を追加する度に、IoTデバイス側で改修が必要になるため、FOTA(Firmware Over The Air)などの遠隔アップデートのような効率的なファームウェア・ソフトウェア管理の仕組みが無い場合は、見逃せないコストに繋がる可能性があります。 そこで、ICGWチームでは、あるエンドポイントにデータを1回送信するだけで、複数のクラウドサービスにデータを転送できる”ミラーリング機能”を開発しました。 記事執筆時点では、ミラーリング機能はベータ機能として一部ユーザのみに提供しており、今回のPoCを通じて試験的に活用し、必要に応じて機能を改善することで品質を高める取り組みを行っています。 ミラーリング機能 ミラーリング機能は、あるエンドポイントにデータを1回送信するだけで、複数のクラウドサービスにデータを転送できる機能です。クラウドサービスの転送設定(スタンダード・イベント・ファンクションの転送設定など)をミラーリングの転送設定へ紐付けることにより、1回のデータ送信で複数のクラウドサービスへデータを転送できるようになります。 今回のPoCでは、ミラーリングの転送設定にストレージ転送設定を紐づけており、オンプレミスストレージ(Minio)とクラウドストレージ(Amazon S3)に対して同時にデータを転送しています。今回はバックアップ用途で使用していますが、通信データのキャプチャ用途で使用し、ミラーリング機能の転送先クラウドサービスでログ解析を行うなど、問題切り分けやセキュリティにこのミラーリング機能が寄与できると考えています。 コンフィグマネージャー コンフィグマネージャーは、IoTデバイスごとの設定ファイルの生成と配信ができる機能です。IoTデバイス毎の設定ファイルの生成および配信設定は、ポータル上から簡単に行えます。 今回のPoCでは、カメラ画像のアップロード間隔を変更するためにコンフィグマネージャーを使用しており、IoTデバイスの要件に応じた設定ファイルの配信を実現しています。今回はアップロード間隔の変更に使用していますが、設定ファイルの内容次第で様々な要件に対応できます。 Eventdriven 中央拠点にUploadされた画像のEventをTriggerに推論するまでのEventを制御する部分となります。 ポイントとしては3つあります。 Kubernetes上で開発を効率的にするためAzureFunctionsを使用 複数のEventをハンドルするためにAzure EventGridを使用 これらはAzureArcの機能の一部としてオンプレでも使用可能 概要 要素技術の説明(Azure Arc Enabled K8s) 今回、Eventdrivenの機能を実装するにあたり Azure Arc Enabled K8sを使用しています。 主要な機能が5つあり、オンプレ/パブリッククラウドなど実行場所を広く選べる使用感の高さが魅力です。 またこの Azure Arc Enabled K8s には AzureFunctionsとEventgridの機能がリリースされており、概要としては以下となっています。 Eventdriven説明 これらの機能を用いてEventdrivenの機能を実装しており、 概要フローとしては以下となっています。 また概要フローの中には、さらに2つのサブフローが存在し、それぞれについて詳しくご紹介します。 イベントの送信 以下のようにAzureClIを使って、EventGridのリソースを作成できます。 ルーティング先のEndpointやPathを意識していれば簡単に作成できると思います。 実際の画面はこんな感じです。 推論の実行 また、Eventがルーティングされたあとに推論処理をトリガーするAzureFunctionsの展開方法についても概要を説明いたします。 Inference TriggerされたEventから推論する部分です。 ポイントとしては2つあります。 15種類の画像を推論するため、GPUの割当効率を考える必要がある。 この問題をマルチインスタンスGPUを用いて解決している。 概要 要素技術の説明(Mig) 上記での問題についてマルチインスタンスGPUを用いて解決しています。 まとめ 以下に今回用いた技術をまとめてみます。 本記事では、IoTアーキテクチャのユースケース検証と、Azureの新技術の調査・検証を目的としたPoCプロジェクトについて説明しました。 この検証を通じて、IoTアーキテクチャにおける具体的なユースケースの適用可能性を技術的観点から検証し、 EdgeにおけるICGWとAzureの主要技術(Azure Arc Enabled Kubernetes)の特徴や利点、適用上の注意点などが参考になれば幸いです。
アバター
はじめに こんにちは、インターン生の 魏心宇 と申します。 2023 年 2 月 6 日から 2 週間にわたって、NTT Com の現場受け入れ型インターンシップに参加させていただきました。 普段は大学で SR (セグメントルーティング) を用いたトラフィックエンジニアリングについて研究しています。 今回のインターンシップでは「SR を用いたキャリアネットワークの開発」をテーマに、NTT Com 発の OSS である Pola PCE への SRv6 機能実装・検証に取り組みました。 この記事では、2 週間の開発体験をご紹介します! インターンシップに参加したモチベーション 私は学部 3 年の冬、 NTT Com TechWorkshop 「プロのネットワークエンジニアと学ぶ!ISP ネットワークのつくりかた」 に参加したことがきっかけで、SR という斬新なネットワークアーキテクチャを知り、技術の素晴らしさに感銘を受けました。 SR を利用することで、従来では実現困難であった複雑な経路制御が簡単に実現可能となるため、国境を超えた高品質なネットワーク回線や特定のアプリに対するネットワークの混雑解消など様々な可能性を生み出し、新たなネットワークサービスを開拓できると期待しています。 NTT Com の R&D 部門であるイノベーションセンターでは SR をはじめとする次世代インターネット技術の研究開発が行われており、独自のアーキテクチャである Multi-AS SR をはじめとした先進的な社会実装の最前線となっています。 大学院に入り、日々の研究とネットワーク運用を通して最先端技術に触れる機会が増える中で、企業ではどのようなネットワーク技術が求められ活用されているのかを学びたいと思うようになり、今回のインターンシップに応募いたしました。 インターンシップで取り組んだこと 今回のインターンシップでは、SR-TE を一元的に管理するための OSS 実装である Pola PCE に対する SRv6 機能の追加と、複数のベンダーの機器(Cisco、Juniper)との相互接続を目標として定めました。 インターンシップの前提として、NTT Com イノベーションセンターではマルチベンダー機器環境で SR を検証しています。 検証内容については Multi-AS Segment Routing 検証連載 で公開されています。 SR、SR-TE、PCE とは何かについて詳しく紹介されているため是非参考にしてください。 [Multi-AS Segment Routing 検証連載#10] PCE を用いた SR-TE の一元管理 で紹介されている通り、SR-TE の実装には各 PE ルーターへの設定が必要であるため、台数が増えると管理が複雑になります。 そこでコントローラーを用いた SR-TE の一元管理により、パス計算の効率化や機器設定の簡素化などが期待できます。 Path Computation Element(PCE)は、前述した SR-TE の一元管理を実現するための技術です。 PCE には発行済み LSP/SR Policy を管理しない Stateless PCE と、これらの情報を管理する Stateful PCE が存在します。 Stateful PCE は既存の LSP/SR Policy を管理するため、それらを条件として考慮した複雑な経路を計算することが可能となります。 また、Stateful PCE には、クライアントである PCC からのリクエストに基づいた経路計算のみを行う Passive Stateful PCE と、自ら経路を発行し PCC へ送信する Active Stateful PCE が存在します。 Active Stateful PCE は新規 SR Policy の発行や既存 SR Policy の更新などの操作ができるため、SR Policy の集中的な管理に適しています。 PCE-PCC の間や複数の PCE 間の通信は、 RFC5440 により定義される Path Computation Element Protocol(PCEP)を用いて行われます。 Pola PCE は、SR-TE の一元管理を実現するための Stateful PCE であり、Active Stateful PCE として動作し、SR-TE の一元管理を実現します。 Pola PCE の概要は Pola PCE で SR 網の TE を体験してみよう! にて紹介されていますので、ぜひ参考にしてみてください。 インターンシップ参加時点では、Pola PCE の実装は SR-MPLS のみを対象としていました。 そのため、Pola PCE を Multi-vendor で動作する SRv6 PCE へと拡張することを今回のインターンシップの目標と定めました。 PCEP プロトコルの SRv6 拡張は draft-ietf-pce-segment-routing-ipv6 にて定義されています。 まだ Internet-Draft で議論中の技術であるため、各ベンダーの SRv6 PCC 機能の実装状況を確認しつつ、開発内容を調整する必要があります。 以下では、PCEP プロトコルの SRv6 拡張およびインターンシップでの実装内容を具体的に紹介します。 PCEP Extensions for SRv6 draft-ietf-pce-segment-routing-ipv6 では、RFC8664 と比べて主に以下の内容が追加されています。 SRv6 PCE Capability sub-TLV PCC/PCE は、PCEP セッション初期化フェーズ(OPEN object)において、 PATH-SETUP-TYPE-CAPILITY TLV に含まれる新しい Path Setup Type: 3 と新しい SRv6-PCE-CAPABILITY sub-TLV を介して、SRv6 をサポートする能力を示します。 SRv6-ERO Subobject SRv6 をサポートするために、ERO オブジェクトに含まれる新しい SRv6-ERO サブオブジェクトが定義されました。 SRv6-RRO Subobject SRv6 をサポートするために、RRO オブジェクトに含まれる新しい SRv6-RRO サブオブジェクトが定義されました。 実装内容 今回のインターンシップでの実装内容について、Pola PCE への SRv6-ERO Subobject の追加 を例に説明します。 パケットキャプチャによるプロトコルの確認・理解 RFC 準拠のコントローラー&プロトコルライブラリ開発の進め方 で紹介されている通り、PCEP メッセージは単一の Common header と複数の Object から構成されます。 それをより直感的に理解するため、関連の RFC を読みながら、パケットキャプチャツールである Wireshark を使って PCEP メッセージの構造を確認します。 上図は、実際に PCC から PCE 宛てに送信された PCRpt メッセージのパケットキャプチャです。 図の通り、PCEP メッセージは、1 つの Common header と複数の Object から構成されています。 Common header にはそれがどの PCEP メッセージであるかを示す Message type が格納されており、今回の例で Common header の Message type は 10 で、 RFC8231 の 8.2 節の定義の通り、PCRpt メッセージであることが分かります。 そして各 Object は Common object header と Object body から構成されていて、Common object header には、その Object を示す Object class と Object type が格納されています。 今回の例では Object class は 7、Object type は 1 であり、 RFC5440 の 7.9 節を参照すると Explicit Route Object (ERO)であることが分かります。 上図の ERO Object の内容を詳しく確認すると、黄色でハイライトされいる Non defined subobject (40) が 2 つあります。内容は次の通りです。 SRv6 関連の Subobject はまだ Internet-Draft で議論中のため、Wireshark ではまだうまく認識されていません。 そこで前述の draft-ietf-pce-segment-routing-ipv6 の 4.3.1 節を参照すると、SRv6-ERO Subobject は下記のように定義されています。 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |L| Type=40 | Length | NT | Flags |V|T|F|S| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved | Endpoint Behavior | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | SRv6 SID (optional) | | (128-bit) | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // NAI (variable, optional) // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SID Structure (optional) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ パケットキャプチャの内容を見ると、 Non defined subobject (40) の部分の 1 バイト目は A8 で、2 進数で変換すると 1010 1000 になります。 SRv6-ERO Subobject の定義と合わせて見ると、 1010 1000 の 1 ビット目は'L' Flag の値、2 ビット目から 8 ビット目の 0101000 は Type の値で 10 進数の 40 と合致しているため、SRv6-ERO Subobject は、 Non defined subobject (40) と表示されていることが分かります。 プロトコルライブラリの実装 - Pola PCE への SRv6-ERO Subobject の追加 これから Pola PCE へ SRv6-ERO Subobject を追加します。 まず、 pkg/packet/pcep/object.go に SRv6-ERO Subobject の定義を追加します。 const ERO_SUBOBJECT_SRV6 uint8 = 0x28 const ( NT_MUST_NOT_BE_INCLUDED uint8 = 0x00 // draft-ietf-pce-segment-routing-ipv6 4.3.1 NT_SRV6_NODE uint8 = 0x02 // draft-ietf-pce-segment-routing-ipv6 4.3.1 NT_SRV6_ADJACENCY_GLOBAL uint8 = 0x04 // draft-ietf-pce-segment-routing-ipv6 4.3.1 NT_SRV6_ADJACENCY_LINKLOCAL uint8 = 0x06 // draft-ietf-pce-segment-routing-ipv6 4.3.1 ) 次に、 draft-ietf-pce-segment-routing-ipv6 の 4.3.1 節を参照し、SRv6-ERO Subobject の構造体とパケット操作に関するメソッドを追加します。 パケット操作メソッドとしては、バイト列を構造体に格納する DecodeFromBytes() 、構造体をバイト列に変換する Serialize() の 2 つのメソッドと、新たに構造体を作成する NewSRv6EroSubObject 関数と SRv6-ERO Subobject から SID を抽出する ToSegment() メソッドが必要となります。 type SRv6EroSubobject struct { LFlag bool SubobjectType uint8 Length uint8 NaiType uint8 VFlag bool TFlag bool FFlag bool SFlag bool Behavior uint16 Segment table.SegmentSRv6 Nai netip.Addr } func (o *SRv6EroSubobject) DecodeFromBytes(subObj [] uint8 ) error { o.LFlag = (subObj[ 0 ] & 0x80 ) != 0 o.SubobjectType = subObj[ 0 ] & 0x7f o.Length = subObj[ 1 ] o.NaiType = subObj[ 2 ] >> 4 o.VFlag = (subObj[ 3 ] & 0x08 ) != 0 o.TFlag = (subObj[ 3 ] & 0x04 ) != 0 o.FFlag = (subObj[ 3 ] & 0x02 ) != 0 o.SFlag = (subObj[ 3 ] & 0x01 ) != 0 o.Behavior = binary.BigEndian.Uint16(subObj[ 6 : 8 ]) sid, _ := netip.AddrFromSlice(subObj[ 8 : 24 ]) o.Segment = table.NewSegmentSRv6(sid) if o.NaiType == 2 { o.Nai, _ = netip.AddrFromSlice(subObj[ 24 : 40 ]) } return nil } func (o *SRv6EroSubobject) Serialize() [] uint8 { buf := make ([] uint8 , 4 ) buf[ 0 ] = o.SubobjectType if o.LFlag { buf[ 0 ] = buf[ 0 ] | 0x80 } buf[ 1 ] = o.Length buf[ 2 ] = o.NaiType * 16 if o.VFlag { buf[ 3 ] = buf[ 3 ] | 0x08 } if o.TFlag { buf[ 3 ] = buf[ 3 ] | 0x04 } if o.FFlag { buf[ 3 ] = buf[ 3 ] | 0x02 } if o.SFlag { buf[ 3 ] = buf[ 3 ] | 0x01 } reserved := make ([] uint8 , 2 ) behavior := make ([] uint8 , 2 ) binary.BigEndian.PutUint16(behavior, o.Behavior) byteSid := o.Segment.Sid.AsSlice() byteSRv6EroSubobject := AppendByteSlices(buf, reserved, behavior, byteSid) return byteSRv6EroSubobject } func (o *SRv6EroSubobject) Len() ( uint16 , error ) { // The Length MUST be at least 24, and MUST be a multiple of 4. // An SRv6-ERO subobject MUST contain at least one of a SRv6-SID or an NAI. if o.NaiType == NT_MUST_NOT_BE_INCLUDED { // Type, Length, Flags (4byte) + Reserved(2byte) + Behavior(2byte) + SID (16byte) return uint16 ( 24 ), nil } else if o.NaiType == NT_IPV6_NODE { // Type, Length, Flags (4byte) + Reserved(2byte) + Behavior(2byte) + SID (16byte) + Nai (16byte) return uint16 ( 40 ), nil } else { return uint16 ( 0 ), errors.New( "unsupported naitype" ) } } func NewSRv6EroSubObject(seg table.SegmentSRv6) (*SRv6EroSubobject, error ) { subo := &SRv6EroSubobject{ LFlag: false , SubobjectType: ERO_SUBOBJECT_SRV6, NaiType: NT_MUST_NOT_BE_INCLUDED, VFlag: false , TFlag: false , FFlag: true , SFlag: false , Behavior: uint16 ( 1 ), Segment: seg, } length, err := subo.Len() if err != nil { return subo, err } subo.Length = uint8 (length) return subo, nil } func (o *SRv6EroSubobject) ToSegment() table.Segment { return o.Segment } そして、ERO Object から SR-ERO Subobject と SRv6-ERO Subobject を区別するために、ERO Object の DecodeFromBytes() メソッドを修正します。 func (o *EroObject) DecodeFromBytes(typ uint8 , objectBody [] uint8 ) error { o.ObjectType = typ if len (objectBody) == 0 { return nil } for { var eroSubobj EroSubobject if (objectBody[ 0 ] & 0x7f ) == 36 { eroSubobj = &SREroSubobject{} } else if (objectBody[ 0 ] & 0x7f ) == 40 { eroSubobj = &SRv6EroSubobject{} } else { return errors.New( "invalid Subobject type" ) } if err := eroSubobj.DecodeFromBytes(objectBody); err != nil { return err } o.EroSubobjects = append (o.EroSubobjects, eroSubobj) if objByteLength, err := eroSubobj.Len(); err != nil { return err } else if int (objByteLength) < len (objectBody) { objectBody = objectBody[objByteLength:] } else if int (objByteLength) == len (objectBody) { break } else { return errors.New( "srerosubobject parse error" ) } } return nil } ERO Object の新構造体を作成する NewEroSubobject 関数も同様に修正します。 func NewEroSubobject(seg table.Segment) (EroSubobject, error ) { if v, ok := seg.(table.SegmentSRMPLS); ok { subo, err := NewSREroSubObject(v) if err != nil { return nil , err } return subo, nil } else if v, ok := seg.(table.SegmentSRv6); ok { subo, err := NewSRv6EroSubObject(v) if err != nil { return nil , err } return subo, nil } else { return nil , errors.New( "invalid Segment type" ) } } これにより、Pola PCE へ SRv6-ERO Subobject に対する処理機能を追加できました。 以上では、Pola PCE への SRv6-ERO Subobject の追加を例に、今回のインターンシップでの Pola PCE 開発の流れを紹介しました。 それ以外にも、SRv6 に対応するため、Pola PCE における LSP Object の IPV6-LSP-IDENTIFIERS TLV の追加や、explicit SRv6-TE Policy を扱うための関数の修正などの変更を加えました。 詳しい説明は記事の長さの都合上、割愛しますが、ご興味のある方はこちらの PR をご参照ください。 動作検証 本章では、作成した実装を用い、複数のベンダーの機器(Cisco、Juniper)との相互接続検証を実施します。 検証環境は上図の通りで、環境の構築には OSS ツールの Containerlab を使用しました。 Containerlab を用いることで、YAML ファイルベースで簡単に検証環境を構築できます。 今回の検証用の Containerlab 設定ファイルはこちらの Repo で公開しています。 ぜひご活用ください。 SRv6 機能を追加した Pola PCE とベンダー機器との相互接続試験を行います。 まずは Containerlab の動作状態を確認します。 % sudo containerlab inspect INFO[0000] Parsing & checking topology file: nttcom-internship.clab.yml +---+---------------------------------+--------------+--------------------------------+--------+---------+----------------+----------------------+ | # | Name | Container ID | Image | Kind | State | IPv4 Address | IPv6 Address | +---+---------------------------------+--------------+--------------------------------+--------+---------+----------------+----------------------+ | 1 | clab-nttcom-internship-host01 | 2758bf898616 | wbitt/network-multitool:latest | linux | running | 172.20.20.5/24 | 2001:172:20:20::5/64 | | 2 | clab-nttcom-internship-host02 | ee82fd3f4bba | wbitt/network-multitool:latest | linux | running | 172.20.20.2/24 | 2001:172:20:20::2/64 | | 3 | clab-nttcom-internship-host03 | f2747f464ffd | wbitt/network-multitool:latest | linux | running | 172.20.20.8/24 | 2001:172:20:20::8/64 | | 4 | clab-nttcom-internship-pcc-rt01 | acb1ba1cb010 | vrnetlab/vr-vmx:22.4R1.10 | vr-vmx | running | 172.20.20.7/24 | 2001:172:20:20::7/64 | | 5 | clab-nttcom-internship-pcc-rt02 | 1928d17fae80 | ios-xr/xrd-control-plane:7.8.1 | xrd | running | 172.20.20.3/24 | 2001:172:20:20::3/64 | | 6 | clab-nttcom-internship-pcc-rt03 | f29848103688 | vrnetlab/vr-vmx:22.4R1.10 | vr-vmx | running | 172.20.20.6/24 | 2001:172:20:20::6/64 | | 7 | clab-nttcom-internship-pola-pce | 0dc707b9dd09 | ghcr.io/nttcom/pola:latest | linux | running | 172.20.20.9/24 | 2001:172:20:20::9/64 | | 8 | clab-nttcom-internship-xtc-pce | f31bc03d953a | ios-xr/xrd-control-plane:7.8.1 | xrd | running | 172.20.20.4/24 | 2001:172:20:20::4/64 | +---+---------------------------------+--------------+--------------------------------+--------+---------+----------------+----------------------+ 次に、Pola PCE のコンテナで Pola PCE を起動し、動作を確認します。 % sudo docker exec -it clab-nttcom-internship-pola-pce bash root@pola-pce:~# cat polad.yaml # polad.yaml global: pcep: address: "[fc00::ffff]" port: 4189 grpc-server: address: "127.0.0.1" port: 50051 log: path: "/var/log/pola/" name: "polad.log" ted: enable: false root@pola-pce:~# polad > /var/log/polad.log 2>&1 & [1] 65 root@pola-pce:~# ps -aux | grep polad root 65 0.0 0.0 1083748 11216 pts/1 Sl 07:18 0:00 polad root 73 0.0 0.0 3468 1720 pts/1 S+ 07:19 0:00 grep polad pola session コマンドにより PCEP Session を確認します。 root@pola-pce:~# pola session sessionAddr(1): fc00::1 sessionAddr(2): fc00::2 sessionAddr(3): fc00::3 pola sr-policy list コマンドにより各ルーター既存の SR Policy を確認します。 root@pola-pce:~# pola sr-policy list Session: fc00::1 PolicyName: vrf-100-policy-pcc-rt03 SrcAddr: fd00:ffff::1 DstAddr: 2a0b:2542:400:3:1:: Color: 100 Preference: 100 SegmentList: 2a0b:2542:400:3:1:: Session: fc00::2 PolicyName: cfg_vrf-100-policy-pcc-rt03_discr_10 SrcAddr: fd00:ffff::2 DstAddr: 2a0b:2542:400:3:1:: Color: 100 Preference: 10 SegmentList: None Session: fc00::3 PolicyName: vrf-100-policy-pcc-rt01 SrcAddr: fd00:ffff::3 DstAddr: 2a0b:2542:400:1:1:: Color: 100 Preference: 100 SegmentList: 2a0b:2542:400:1:1:: PolicyName: vrf-100-policy-pcc-rt02 SrcAddr: fd00:ffff::3 DstAddr: 2a0b:2542:400:2:1:: Color: 100 Preference: 100 SegmentList: 2a0b:2542:400:2:1:: SR Policy のパラメータを指定する yaml ファイルを確認します。 root@pola-pce:~# cat policy_explicit_rt01.yaml # policy_explicit_rt01.yaml srPolicy: pcepSessionAddr: "fc00::1" srcAddr: "fd00:ffff::1" dstAddr: "2a0b:2542:400:2:1::" name: vrf-100-policy-pcc-rt02-by-pola color: 100 segmentList: - sid: "2a0b:2542:400:3:1::" nai: "fd00:ffff::3" - sid: "2a0b:2542:400:2:1::" nai: "fd00:ffff::2" root@pola-pce:~# cat policy_explicit_rt02.yaml # policy_explicit_rt02.yaml srPolicy: pcepSessionAddr: "fc00::2" srcAddr: "fd00:ffff::2" dstAddr: "2a0b:2542:400:1:1::" name: vrf-100-policy-pcc-rt01-by-pola color: 100 segmentList: - sid: "2a0b:2542:400:3:1::" nai: "fd00:ffff::3" - sid: "2a0b:2542:400:1:1::" nai: "fd00:ffff::1" pola sr-policy add コマンドにより SR Policy を新たに発行します。 root@pola-pce:~# pola sr-policy add -f policy_explicit_rt01.yaml -l success! root@pola-pce:~# pola sr-policy add -f policy_explicit_rt02.yaml -l success! 再び pola sr-policy list コマンドを実行し各ルーターの SR Policy を確認します。 root@pola-pce:~# pola sr-policy list Session: fc00::1 PolicyName: vrf-100-policy-pcc-rt03 SrcAddr: fd00:ffff::1 DstAddr: 2a0b:2542:400:3:1:: Color: 100 Preference: 100 SegmentList: 2a0b:2542:400:3:1:: PolicyName: vrf-100-policy-pcc-rt02-by-pola SrcAddr: fd00:ffff::1 DstAddr: 2a0b:2542:400:2:1:: Color: 100 Preference: 100 SegmentList: 2a0b:2542:400:3:1:: -> 2a0b:2542:400:2:1:: Session: fc00::2 PolicyName: cfg_vrf-100-policy-pcc-rt03_discr_10 SrcAddr: fd00:ffff::2 DstAddr: 2a0b:2542:400:3:1:: Color: 100 Preference: 10 SegmentList: None Session: fc00::3 PolicyName: vrf-100-policy-pcc-rt01 SrcAddr: fd00:ffff::3 DstAddr: 2a0b:2542:400:1:1:: Color: 100 Preference: 100 SegmentList: 2a0b:2542:400:1:1:: PolicyName: vrf-100-policy-pcc-rt02 SrcAddr: fd00:ffff::3 DstAddr: 2a0b:2542:400:2:1:: Color: 100 Preference: 100 SegmentList: 2a0b:2542:400:2:1:: 上記の pola sr-policy add コマンドおよび pola sr-policy list コマンドを実行すると、Pola PCE から以下のようなログが出力されます。 2023-04-14T11:39:28.367Z info Received Keepalive {"session": "fc00::1"} 2023-04-14T11:39:46.795Z info received CreateSRPolicy API request {"input": "{\"SRPolicy\":{\"pcepSessionAddr\":\"/AAAAAAAAAAAAAAAAAAAAQ==\",\"srcAddr\":\"/QD//wAAAAAAAAAAAAAAAQ==\",\"dstAddr\":\"KgslQgQAAAIAAQAAAAAAAA==\",\"color\":100,\"policyName\":\"vrf-100-policy-pcc-rt02-by-pola\",\"segmentList\":[{\"sid\":\"2a0b:2542:400:3:1::\"},{\"sid\":\"2a0b:2542:400:2:1::\"}]}}", "server": "grpc"} 2023-04-14T11:39:46.795Z info Send PCInitiate {"session": "fc00::1"} 2023-04-14T11:39:46.895Z info Received PCRpt {"session": "fc00::1"} 2023-04-14T11:39:46.895Z info Finish Stateful PCE request {"session": "fc00::1", "srpID": 1} 2023-04-14T11:39:48.829Z info Send Keepalive {"session": "fc00::2"} 2023-04-14T11:39:48.837Z info Received Keepalive {"session": "fc00::2"} 2023-04-14T11:39:51.430Z info received CreateSRPolicy API request {"input": "{\"SRPolicy\":{\"pcepSessionAddr\":\"/AAAAAAAAAAAAAAAAAAAAg==\",\"srcAddr\":\"/QD//wAAAAAAAAAAAAAAAg==\",\"dstAddr\":\"KgslQgQAAAEAAQAAAAAAAA==\",\"color\":100,\"policyName\":\"vrf-100-policy-pcc-rt01-by-pola\",\"segmentList\":[{\"sid\":\"2a0b:2542:400:3:1::\"},{\"sid\":\"2a0b:2542:400:1:1::\"}]}}", "server": "grpc"} 2023-04-14T11:39:51.430Z info Send PCInitiate {"session": "fc00::2"} 2023-04-14T11:39:56.697Z info Send Keepalive {"session": "fc00::3"} 2023-04-14T11:39:56.778Z info Received Keepalive {"session": "fc00::3"} 2023-04-14T11:39:58.285Z info Send Keepalive {"session": "fc00::1"} 2023-04-14T11:40:06.190Z info Receive GetSRPolicyList API request {"server": "grpc"} 2023-04-14T11:40:06.190Z info Send SRPolicyList API reply {"server": "grpc"} 2023-04-14T11:40:16.903Z info Received Keepalive {"session": "fc00::1"} 確認した結果、Pola PCE と各ルーターとの PCEP Session が確立されており、Juniper vMX ルーターである pcc-rt01 の SR Policy が発行され、pcc-rt01 からの既存 SR Policy のレポートも正しく受け取っていることがわかります。 Cisco IOS XRd ルーターである pcc-rt02 については既存 SR Policy のレポートが受け取れておらず、Pola PCE から pcc-rt02 に対して SR Policy を発行できそうに見えますが、pcc-rt02 側が正しく受け取れているかどうかは不明のため、もっと詳細を調べる必要があります。 Wireshark を利用して PCEP パケットをキャプチャします。 Containerlab 環境からのパケットキャプチャは以下のように行います。 Windows WSL2 環境から SSH 経由でパケットキャプチャをする場合のコマンド例: ssh $clab_host "sudo ip netns exec clab-nttcom-internship-pola-pce tcpdump -U -nni eth1 -w -" | /mnt/c/Program\ Files/Wireshark/wireshark.exe -k -i - macOS/Linux 環境から SSH 経由でパケットキャプチャをする場合のコマンド例: ssh $clab_host "sudo ip netns exec clab-nttcom-internship-pola-pce tcpdump -U -nni eth1 -w -" | wireshark -k -i - Cisco IOS XRd との相互接続検証 Cisco IOS XRd ルーターである pcc-rt02 側でのパケットキャプチャ結果を以下に示します。 パケットキャプチャ結果を確認すると、Pola PCE の実装通りに PCInitiate メッセージが送られましたが、pcc-rt02 からは PCReport メッセージが返ってきていません。 pcc-rt02 で show コマンドによって SR Policy の状態を確認すると、以下のように新規発行された SR Policy は存在していないことがわかります。 RP/0/RP0/CPU0:pcc-rt02#show segment-routing traffic-eng policy Fri Apr 14 13:43:15.021 UTC SR-TE policy database --------------------- Color: 100, End-point: 2a0b:2542:400:3:1:: Name: srte_c_100_ep_2a0b:2542:400:3:1:: Status: Admin: up Operational: down for 02:52:28 (since Apr 14 10:50:46.274) Candidate-paths: Preference: 10 (configuration) (inactive) Name: vrf-100-policy-pcc-rt03 Last error: SRv6 locator does not support USID Requested BSID: dynamic PCC info: Symbolic name: cfg_vrf-100-policy-pcc-rt03_discr_10 PLSP-ID: 1 Constraints: Protection Type: protected-preferred Maximum SID Depth: 19 Explicit: segment-list vrf-100-segment-list-pcc-rt03 (inactive) Weight: 10, Metric Type: TE SID[0]: 2a0b:2542:400:3:1:: SRv6 Information: Locator: WOLFLAB Binding SID requested: Dynamic Binding SID behavior: End.B6.Insert.Red Attributes: Forward Class: 0 Steering labeled-services disabled: no Steering BGP disabled: no IPv6 caps enable: yes Invalidation drop enabled: no Max Install Standby Candidate Paths: 0 また、初期設定による既存の SR Policy である vrf-100-policy-pcc-rt03 に関しても、 Last error: SRv6 locator does not support USID が原因で (inactive) が表示されます。 pcc-rt02 の SRv6 関連設定は以下のようになっています。 RP/0/RP0/CPU0:pcc-rt02#show running-config segment-routing Fri Apr 14 13:44:27.988 UTC segment-routing traffic-eng segment-lists srv6 sid-format usid-f3216 ! segment-list vrf-100-segment-list-pcc-rt03 srv6 index 10 sid 2a0b:2542:400:3:1:: ! ! ! policy vrf-100-policy-pcc-rt03 srv6 locator WOLFLAB binding-sid dynamic behavior ub6-insert-reduced ! color 100 end-point ipv6 2a0b:2542:400:3:1:: candidate-paths preference 10 explicit segment-list vrf-100-segment-list-pcc-rt03 weight 10 ! ! ! ! pcc source-address ipv6 fc00::2 pce address ipv6 fc00::ffff ! report-all ! ! srv6 locators locator WOLFLAB prefix 2a0b:2542:400:2::/64 ! ! ! ! pola sr-policy list コマンドによる既存 SR Policy の確認結果では、 SegmentList: None と表示され、SR Policy が学習されていません。 Cisco のドキュメント Segment Routing Configuration Guide for Cisco ASR 9000 Series Routers, IOS XR Release 7.8.x を参照したところ、インターンシップ参加時点(2023年2月)の最新版である Cisco IOS XR 7.8.1 では SRv6 policy が uSID 以外の SID 形式をサポートしていないようであり、これが原因と考えられます。 また同様に、Cisco のドキュメントからはインターンシップ参加時点で PCE-initiated SRv6 policies via PCEP 機能がサポートされていないことがわかります。 以上が原因で残念ながら今回のインターンシップでは Cisco IOS XRd との接続検証は成功しませんでした。 Juniper vMX との相互接続検証 Juniper vMX ルーターである pcc-rt01 側でのパケットキャプチャ結果を以下に示します。 パケットキャプチャ結果を確認すると、Pola PCE の実装通りに PCInitiate メッセージが送られ、pcc-rt01 から PCReport メッセージが返ってきていることがわかります。 pcc-rt01 で show コマンドによって SR Policy の状態を確認すると、以下のように新規発行された SR Policy である vrf-100-policy-pcc-rt02-by-pola が存在していることがわかります。 admin@pcc-rt01> show spring-traffic-engineering lsp To State LSPname 2a0b:2542:400:2:1::-100<c6> Up vrf-100-policy-pcc-rt02-by-pola 2a0b:2542:400:3:1::-100<c6> Up vrf-100-policy-pcc-rt03 Total displayed LSPs: 2 (Up: 2, Down: 0) 最後に、疎通確認を行います。 % sudo docker exec -it clab-nttcom-internship-host01 /bin/bash bash-5.1# ping -c 3 fd00:192:168:2::1 PING fd00:192:168:2::1(fd00:192:168:2::1) 56 data bytes 64 bytes from fd00:192:168:2::1: icmp_seq=1 ttl=62 time=4.00 ms 64 bytes from fd00:192:168:2::1: icmp_seq=2 ttl=62 time=3.61 ms 64 bytes from fd00:192:168:2::1: icmp_seq=3 ttl=62 time=3.77 ms --- fd00:192:168:2::1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003ms rtt min/avg/max/mdev = 3.610/3.795/4.003/0.161 ms host01 から host02 への経路は、SR Policy(vrf-100-policy-pcc-rt02-by-pola) により pcc-rt01 -> pcc-rt03 -> pcc-rt02(緑色の線)経由になると期待されます。 pcc-rt03 の ge-0/0/0 でパケットキャプチャし、SR Policy の挙動を確認します。 % sudo ip netns exec clab-nttcom-internship-pcc-rt03 tcpdump -nni eth1 -v ip6 tcpdump: listening on eth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes 00:22:59.940361 IP6 (flowlabel 0x47e1e, hlim 255, next-header Routing (43) payload length: 128) fd00:ffff::1 > 2a0b:2542:400:3:1::: RT6 (len=2, type=4, segleft=1, last-entry=0, flags=0x0, tag=0, [0]2a0b:2542:400:2:40::) IP6 (flowlabel 0xe793a, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:1::1 > fd00:192:168:2::1: [icmp6 sum ok] ICMP6, echo request, id 13, seq 1 00:23:00.939071 IP6 (flowlabel 0x47e1e, hlim 255, next-header Routing (43) payload length: 128) fd00:ffff::1 > 2a0b:2542:400:3:1::: RT6 (len=2, type=4, segleft=1, last-entry=0, flags=0x0, tag=0, [0]2a0b:2542:400:2:40::) IP6 (flowlabel 0xe793a, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:1::1 > fd00:192:168:2::1: [icmp6 sum ok] ICMP6, echo request, id 13, seq 2 00:23:01.941300 IP6 (flowlabel 0x47e1e, hlim 255, next-header Routing (43) payload length: 128) fd00:ffff::1 > 2a0b:2542:400:3:1::: RT6 (len=2, type=4, segleft=1, last-entry=0, flags=0x0, tag=0, [0]2a0b:2542:400:2:40::) IP6 (flowlabel 0xe793a, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:1::1 > fd00:192:168:2::1: [icmp6 sum ok] ICMP6, echo request, id 13, seq 3 3 packets captured 3 packets received by filter 0 packets dropped by kernel 結果の通り、pcc-rt03 でパケットキャプチャして確認すると、pcc-rt01 から pcc-rt03 へ ICMP Request パケットが想定した経路で送られていることがわかります。 また、host02 から host01 へ ICMP Reply パケットについて、前述の原因により pcc-rt02 側で有効な SR Policy が存在しないため、経路は IGP に従って pcc-rt02 -> pcc-rt01(青色の線)になります。 同様に pcc-rt02 の Gi0/0/0/0 でパケットキャプチャして確認します。 % sudo ip netns exec clab-nttcom-internship-pcc-rt02 tcpdump -nni Gi0-0-0-0 -v ip6 tcpdump: listening on Gi0-0-0-0, link-type EN10MB (Ethernet), snapshot length 262144 bytes 00:45:20.822110 IP6 (flowlabel 0x5aab2, hlim 64, next-header IPv6 (41) payload length: 104) fd00:ffff::2 > 2a0b:2542:400:1:6::: IP6 (flowlabel 0x5aab2, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:2::1 > fd00:192:168:1::1: [icmp6 sum ok] ICMP6, echo reply, id 16, seq 1 00:45:21.820163 IP6 (flowlabel 0x5aab2, hlim 64, next-header IPv6 (41) payload length: 104) fd00:ffff::2 > 2a0b:2542:400:1:6::: IP6 (flowlabel 0x5aab2, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:2::1 > fd00:192:168:1::1: [icmp6 sum ok] ICMP6, echo reply, id 16, seq 2 00:45:22.822313 IP6 (flowlabel 0x5aab2, hlim 64, next-header IPv6 (41) payload length: 104) fd00:ffff::2 > 2a0b:2542:400:1:6::: IP6 (flowlabel 0x5aab2, hlim 63, next-header ICMPv6 (58) payload length: 64) fd00:192:168:2::1 > fd00:192:168:1::1: [icmp6 sum ok] ICMP6, echo reply, id 16, seq 3 3 packets captured 3 packets received by filter 0 packets dropped by kernel キャプチャした結果、ICMP Reply パケットの経路は想定通り pcc-rt02 -> pcc-rt01(青色の線)になっていることがわかります。 これにより、今回のインターンシップで実装した Pola PCE の SRv6 機能が正しく動作していることが確認できました。 まとめと今後の課題 本体験記では、インターンシップで取り組んだ Pola PCE への SRv6 新規機能実装・検証内容をご紹介しました。 前半では、Pola PCE への SRv6-ERO Subobject の追加を例に、今回のインターンシップでの開発の流れについて説明しました。 後半では、インターンシップ中に実施した複数のベンダーの機器(Cisco、Juniper)とPola PCE との相互接続検証を紹介しました。 結果として、Pola PCE へ実装した SRv6 機能が正しく動作していることが確認できました。 今回のインターンシップでは、実装状況の関係から、残念ながら本来の目標であった複数のベンダーの機器との相互接続は実現できませんでした。 SRv6 の TE は今後の発展が期待される分野であり、また私自身の研究にもつながるトピックです。 今後もベンダーの動向に注視しつつ、研究や運用に役立つ機能の開発に積極的に取り組んでいきたいと思います。 また、Pola PCE では次のステップとして、Dynamic SRv6-TE や BGP SR Policy NLRI への対応を検討しています。 これらの技術開発についても引き続き携わりたいと期待しています。 インターンシップの感想 わずか 2 週間のインターンシップでしたが、Pola PCE の開発準備から相互接続検証までの全プロセスに取り組むことができ、大変素晴らしい経験となりました。 Go 言語と PCEP 初心者の私にとっては大きな挑戦でしたが、Pola PCE のコア開発者の三島さんと竹中さんの丁寧な指導のお陰で、数々の課題を乗り越え、インターンシップの最終日までに開発目標を達成できました。 また、チーム全体の定例ミーティングへの参加を通して、Pola PCE の開発者の先輩社員の方だけでなく配属チームの社員の方々と交流でき、チーム全体の雰囲気を感じると共に普段の業務内容をより深く理解できました。 今回インターンシップで獲得した貴重な経験を今後の研究活動に活かしていきたいと思います。 トレーナーの三島さん、竹中さん、上長の木村さん、そしてお世話になった NTT Com の皆さん、2 週間大変貴重な経験をさせていただき、本当にありがとうございました。 トレーナーからのコメント トレーナーを担当したイノベーションセンターの竹中です。2週間のインターンシップお疲れ様でした。 今回のインターンシップでは、魏さんには主に SR PCE である Pola PCE の SRv6 拡張を実施していただきました。プロトコルやパケット構造の理解からはじめ、RFC や Internet-Draft とコードを比較しつつ拡張する部分を検討し、そして実際に機能拡張・動作確認まで行い成功させてくれました。インターン前は PCEP も Go 言語にもあまり馴染みがなかったということでしたが、その中でもプロトコルや実装を即座に理解し、手早く機能拡張されている様子はとても印象的でした。 また、本ブログでは SRv6 機能拡張について触れていただきましたが、それ以外に IPv6 underlay の環境情報を取得するための BGP-LS の各ベンダ実装状況など、PCE を活用する上で必要となる機能を調査していただきました。 SR PCE は SR 技術において様々な Traffic Engineering を行う上で今後重要となる技術です。 今回実施いただいた Pola PCE の SRv6 拡張によって、SRv6 環境においてより柔軟な TE の管理や検証が行えるようになりました。我々の SRv6 環境へ導入し、運用や検証に活用していきます! また、調査いただいたベンダの機能実装状況なども、今後の検証方針の決定やベンダへの問い合わせに活用させていただきます。 このインターンシップを通して得られた様々な知見が魏さんの今後の活動にも役立つことを願っています。 改めて、インターンシップへのご参加とご活躍、ありがとうございました!
アバター
はじめに こんにちは、インターンシップ生の大平です。 2023年2月にNTTコミュニケーションズで開催された2週間の現場受け入れ型インターンシップに参加させていただきました。 普段は大学でエッジコンピューティングに関する研究をしています。 今回のインターンシップでは「SR (セグメントルーティング) を用いたキャリアネットワークの開発」というテーマで、 IPv6 Single/Dual-Stack SR-MPLSを検証しました。 この記事ではインターンシップの体験記として、どのような内容に取り組んだかを紹介します。 インターンシップに参加するまで 以前からカンファレンスネットワークの構築などでNTTコミュニケーションズの取り組みに触れる機会や社員の方とお話しする機会があり、会社の雰囲気やどのような業務をしているのかに興味を持ちインターンシップへの参加を決めました。 また、セグメントルーティングについては、以前から大まかな概要については知っていましたが実際に触れたことはなかったため、この機会に学んでみたいと考えたのも理由の1つです。 インターンシップで取り組んだこと 今回のインターンシップではマルチベンダー(Cisco、Juniper、Nokia)の機器が相互接続したIPv6 Single/Dual-Stack SR-MPLSを検証しました。 NTT Comが提案する mutli-AS SR は、役割や機能、地理的に異なるSR網を複数のASに分離し、疎結合に構成しつつEnd-to-EndにTE を行う独自のアーキテクチャとなっています。 multi-AS SRのユースケースの1つとして、SR-MPLSのASにIPv6 Underlayを敷設することによるSRv6 Migrationがありますが、2022年時点ではSR-MPLS IPv6 Underlayの実用例は存在しません。 そこでこの検証では、IPv6 Underlayで得られるPlug-and-Playなネットワーク構築やSRv6 Migrationなどのメリットを整理するとともに、現時点の実装で出来ることと出来ないことを洗い出すことを目的としています。 インターンシップの最初の1週間では、同一ベンダーの機器同士でIPv6 Single-Stack構成のSR-MPLSを構築しました。 これにより、各ベンダーの機器における基本的なSR-MPLSの機能を理解できました。 次に、マルチベンダーの機器同士でIPv6 Single-Stack構成のSR-MPLSを構築しました。 最後に、IPv4/IPv6 Dual-Stack構成のSR-MPLSを検証し、各ベンダーでできること・できないことの洗い出しを行いました。 以下では、SR-MPLSについてや検証の手順を説明します。 SR-MPLSについて SR-MPLSは、データプレーンにMPLSを利用したセグメントルーティングです。 MPLSはパケットの転送情報をラベルで表現するフォワーディング技術です。 ラベルが付与されたパケットを受け取ったルーターは、MPLS Tableを参照し、ラベルを変換しつつパケットを転送します。 セグメントルーティングはパケットの転送情報をセグメントとして表現し、パケットに付与したセグメントリストに従ってパケットを転送するソースルーティングのパラダイムに沿ったルーティング技術です。 セグメントリストはパケットのヘッダーに追加され、各ルーターはこれに従って転送処理を行います。 このため、従来の経路制御技術よりも柔軟なトラフィックエンジニアリング可能となり、経路の最適化やネットワークリソースの効率的な利用などを実現できます。 検証手順 今回の検証では、下記の図のようなトポロジーを作成します。 このトポロジーでは9つのルーターからなるSR domainをコア網として構成し、3つの顧客を収容することを想定しています。 今回使用する機器は以下のとおりです。 rt01, rt04, rt07: IOS XR 7.4.1 rt02, rt05, rt08: JUNOS 22.3R1.11 rt03, rt06, rt09: SR OS 22.7.R1 v6-cust01, v6-cust02: Ubuntu Server 22.04 この検証では、以下のWebページを参考にIPv6 Underlayを検証しました。 [Multi-AS Segment Routing 検証連載 #1] SR-MPLS L3VPN in Single-AS - NTT Communications Engineers' Blog [Multi-AS Segment Routing 検証連載 #4] Color-Based Steering in Single-AS - NTT Communications Engineers' Blog 基本設定 インターフェースのIPv6有効化とLoopbackインターフェースを設定します。 そして、インターフェースに付与されたリンクローカルアドレスを用いてルータ同士の疎通確認を行います。 IS-ISの設定 IS-ISを用いてSR-MPLSのSID情報を広告します。 各ルーターにIS-ISを設定し、IS-ISネイバーがUpしていることとSID TableにSID情報が登録されていることを確認します。 Cisco IS-ISネイバーの確認 RP/0/RP0/CPU0:v6-rt01#show isis neighbors IS-IS 1 neighbors: System Id Interface SNPA State Holdtime Type IETF-NSF v6-rt04 Gi0/0/0/0 *PtoP* Up 22 L2 Capable v6-rt05 Gi0/0/0/1 *PtoP* Up 25 L2 Capable SID Table (MPLS Table) の確認 RP/0/RP0/CPU0:v6-rt01#show mpls forwarding Local Outgoing Prefix Outgoing Next Hop Bytes Label Label or ID Interface Switched ------ ----------- ------------------ ------------ --------------- ------------ 16002 16002 SR Pfx (idx 2) Gi0/0/0/0 fe80::20c:29ff:fe3e:f7e8 \ 2192 16002 SR Pfx (idx 2) Gi0/0/0/1 fe80::20c:29ff:feb0:78e2 \ 0 16003 16003 SR Pfx (idx 3) Gi0/0/0/1 fe80::20c:29ff:feb0:78e2 \ 2594 16004 Pop SR Pfx (idx 4) Gi0/0/0/0 fe80::20c:29ff:fe3e:f7e8 \ 1501 16005 Pop SR Pfx (idx 5) Gi0/0/0/1 fe80::20c:29ff:feb0:78e2 \ 2662 16006 16006 SR Pfx (idx 6) Gi0/0/0/1 fe80::20c:29ff:feb0:78e2 \ 2602 16007 16007 SR Pfx (idx 7) Gi0/0/0/0 fe80::20c:29ff:fe3e:f7e8 \ 1433 16007 SR Pfx (idx 7) Gi0/0/0/1 fe80::20c:29ff:feb0:78e2 \ 0 16008 16008 SR Pfx (idx 8) Gi0/0/0/0 fe80::20c:29ff:fe3e:f7e8 \ 0 16008 SR Pfx (idx 8) Gi0/0/0/1 fe80::20c:29ff:feb0:78e2 \ 2654 16009 16009 SR Pfx (idx 9) Gi0/0/0/1 fe80::20c:29ff:feb0:78e2 \ 2662 24000 Pop SR Adj (idx 1) Gi0/0/0/1 fe80::20c:29ff:feb0:78e2 \ 0 24001 Pop SR Adj (idx 3) Gi0/0/0/1 fe80::20c:29ff:feb0:78e2 \ 0 24002 Pop SR Adj (idx 1) Gi0/0/0/0 fe80::20c:29ff:fe3e:f7e8 \ 0 24003 Pop SR Adj (idx 3) Gi0/0/0/0 fe80::20c:29ff:fe3e:f7e8 \ 0 24004 Aggregate 100: Per-VRF Aggr[V] \ 100 0 Juniper IS-ISネイバーの確認 user@v6-rt02> show isis adjacency Interface System L State Hold (secs) SNPA ge-0/0/0.0 v6-rt04 2 Up 26 ge-0/0/1.0 v6-rt05 2 Up 18 ge-0/0/2.0 v6-rt06 2 Up 23 SID Table (MPLS Table) の確認 user@v6-rt02> show route table mpls.0 mpls.0: 31 destinations, 31 routes (31 active, 0 holddown, 0 hidden) + = Active Route, - = Last Active, * = Both 0 *[MPLS/0] 00:12:33, metric 1 to table inet.0 0(S=0) *[MPLS/0] 00:12:33, metric 1 to table mpls.0 1 *[MPLS/0] 00:12:33, metric 1 Receive 2 *[MPLS/0] 00:12:33, metric 1 to table inet6.0 2(S=0) *[MPLS/0] 00:12:33, metric 1 to table mpls.0 13 *[MPLS/0] 00:12:33, metric 1 Receive 16 *[VPN/0] 00:12:33 > via lsi.0 (100), Pop 17 *[VPN/0] 00:12:33 > via lsi.1 (200), Pop 18 *[VPN/0] 00:12:33 > via lsi.2 (300), Pop 19 *[L-ISIS/14] 00:11:01, metric 0 > to 10.2.5.5 via ge-0/0/1.0, Pop 19(S=0) *[L-ISIS/14] 00:11:01, metric 0 > to 10.2.5.5 via ge-0/0/1.0, Pop 20 *[L-ISIS/14] 00:11:01, metric 0 > to fe80::20c:29ff:feb0:7800 via ge-0/0/1.0, Pop 20(S=0) *[L-ISIS/14] 00:11:01, metric 0 > to fe80::20c:29ff:feb0:7800 via ge-0/0/1.0, Pop 21 *[L-ISIS/14] 00:10:36, metric 0 > to 10.2.6.6 via ge-0/0/2.0, Pop 21(S=0) *[L-ISIS/14] 00:10:36, metric 0 > to 10.2.6.6 via ge-0/0/2.0, Pop 22 *[L-ISIS/14] 00:10:36, metric 0 > to fe80::38:ffff:fe00:0 via ge-0/0/2.0, Pop 22(S=0) *[L-ISIS/14] 00:10:36, metric 0 > to fe80::38:ffff:fe00:0 via ge-0/0/2.0, Pop 23 *[L-ISIS/14] 00:08:46, metric 0 > to 10.2.4.4 via ge-0/0/0.0, Pop 23(S=0) *[L-ISIS/14] 00:08:46, metric 0 > to 10.2.4.4 via ge-0/0/0.0, Pop 24 *[L-ISIS/14] 00:08:36, metric 0 > to fe80::20c:29ff:fe3e:f7fc via ge-0/0/0.0, Pop 24(S=0) *[L-ISIS/14] 00:08:36, metric 0 > to fe80::20c:29ff:fe3e:f7fc via ge-0/0/0.0, Pop 16001 *[L-ISIS/14] 00:08:31, metric 30 to fe80::20c:29ff:fe3e:f7fc via ge-0/0/0.0, Swap 16001 > to fe80::20c:29ff:feb0:7800 via ge-0/0/1.0, Swap 16001 16003 *[L-ISIS/14] 00:10:36, metric 20 to fe80::20c:29ff:feb0:7800 via ge-0/0/1.0, Swap 16003 > to fe80::38:ffff:fe00:0 via ge-0/0/2.0, Swap 16003 16004 *[L-ISIS/14] 00:08:36, metric 20 > to fe80::20c:29ff:fe3e:f7fc via ge-0/0/0.0, Pop 16004(S=0) *[L-ISIS/14] 00:08:36, metric 20 > to fe80::20c:29ff:fe3e:f7fc via ge-0/0/0.0, Pop 16005 *[L-ISIS/14] 00:11:01, metric 10 > to fe80::20c:29ff:feb0:7800 via ge-0/0/1.0, Pop 16005(S=0) *[L-ISIS/14] 00:11:01, metric 10 > to fe80::20c:29ff:feb0:7800 via ge-0/0/1.0, Pop 16006 *[L-ISIS/14] 00:10:36, metric 10 > to fe80::38:ffff:fe00:0 via ge-0/0/2.0, Swap 16006 16007 *[L-ISIS/14] 00:08:36, metric 30 to fe80::20c:29ff:fe3e:f7fc via ge-0/0/0.0, Swap 16007 > to fe80::20c:29ff:feb0:7800 via ge-0/0/1.0, Swap 16007 16008 *[L-ISIS/14] 00:08:31, metric 20 to fe80::20c:29ff:fe3e:f7fc via ge-0/0/0.0, Swap 16008 to fe80::20c:29ff:feb0:7800 via ge-0/0/1.0, Swap 16008 > to fe80::38:ffff:fe00:0 via ge-0/0/2.0, Swap 16008 16009 *[L-ISIS/14] 00:10:31, metric 20 to fe80::20c:29ff:feb0:7800 via ge-0/0/1.0, Swap 16009 > to fe80::38:ffff:fe00:0 via ge-0/0/2.0, Swap 16009 Nokia IS-ISネイバーの確認 [/] A:user@v6-rt03# show router isis adjacency =============================================================================== Rtr Base ISIS Instance 0 Adjacency =============================================================================== System ID Usage State Hold Interface MT-ID ------------------------------------------------------------------------------- v6-rt06 L2 Up 24 if-to-rt06 0 v6-rt05 L2 Up 21 if-to-rt05 0 ------------------------------------------------------------------------------- Adjacencies : 2 =============================================================================== SID Table (MPLS Table) の確認 [/] A:user@v6-rt03# tools dump router segment-routing tunnel =================================================================================================== 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 | --------------------------------------------------------------------------------------------------+ fd00:ffff::1 Node Orig/Transit 16001 ISIS-0 fe80::20c:29ff:feb0:7814 16001 if-to-rt05 fd00:ffff::2 Node Orig/Transit 16002 ISIS-0 fe80::20c:29ff:feb0:7814 16002 if-to-rt05 fd00:ffff::3 Node Terminating 16003 ISIS-0 fd00:ffff::4 Node Orig/Transit 16004 ISIS-0 fe80::20c:29ff:feb0:7814 16004 if-to-rt05 fd00:ffff::5 Node Orig/Transit 16005 ISIS-0 fe80::20c:29ff:feb0:7814 3 if-to-rt05 fd00:ffff::6 Node Orig/Transit 16006 ISIS-0 fe80::38:ffff:fe00:0 16006 if-to-rt06 fd00:ffff::7 Node Orig/Transit 16007 ISIS-0 fe80::20c:29ff:feb0:7814 16007 if-to-rt05 fd00:ffff::8 Node Orig/Transit 16008 ISIS-0 fe80::20c:29ff:feb0:7814 16008 if-to-rt05 fd00:ffff::9 Node Orig/Transit 16009 ISIS-0 fe80::20c:29ff:feb0:7814 16009 if-to-rt05 fe80::20c:29ff:feb0:7814-"if-to-rt05" Adjacency Transit 524283 ISIS-0 fe80::20c:29ff:feb0:7814 3 if-to-rt05 10.3.5.5 Adjacency Transit 524284 ISIS-0 10.3.5.5 3 if-to-rt05 fe80::38:ffff:fe00:0-"if-to-rt06" Adjacency Transit 524285 ISIS-0 fe80::38:ffff:fe00:0 3 if-to-rt06 10.3.6.6 Adjacency Transit 524287 ISIS-0 10.3.6.6 3 if-to-rt06 --------------------------------------------------------------------------------------------------+ No. of Entries: 13 --------------------------------------------------------------------------------------------------+ VRF、MP-BGP VPNv6の設定 MP-BGP VPNv6を用いて、顧客のVRFの経路を広告します。 各PEルーターにVRF及びMP-BGP VPNv6を設定します。 そして、BGPネイバーの状態と顧客の経路(VRFの経路)が登録されていることを確認します。 Cisco BGPネイバーの確認 RP/0/RP0/CPU0:v6-rt01#show bgp vpnv6 unicast summary BGP router identifier 10.254.1.1, local AS number 65000 BGP generic scan interval 60 secs Non-stop routing is enabled BGP table state: Active Table ID: 0x0 RD version: 0 BGP main routing table version 23 BGP NSR Initial initsync version 20 (Reached) BGP NSR/ISSU Sync-Group versions 0/0 BGP scan interval 60 secs BGP is operating in STANDALONE mode. Process RcvTblVer bRIB/RIB LabelVer ImportVer SendTblVer StandbyVer Speaker 23 23 23 23 23 0 Neighbor Spk AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down St/PfxRcd fd00:ffff::2 0 65000 53 48 23 0 0 00:22:42 1 fd00:ffff::3 0 65000 49 48 23 0 0 00:22:41 1 fd00:ffff::4 0 65000 24 25 23 0 0 00:22:38 0 fd00:ffff::5 0 65000 52 48 23 0 0 00:22:42 0 fd00:ffff::6 0 65000 48 48 23 0 0 00:22:39 0 fd00:ffff::7 0 65000 26 25 23 0 0 00:22:41 1 fd00:ffff::8 0 65000 53 48 23 0 0 00:22:40 1 fd00:ffff::9 0 65000 49 48 23 0 0 00:22:40 1 VRFの経路確認 RP/0/RP0/CPU0:v6-rt01#show route vrf 100 ipv6 Codes: C - connected, S - static, R - RIP, B - BGP, (>) - Diversion path D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 E1 - OSPF external type 1, E2 - OSPF external type 2, E - EGP i - ISIS, L1 - IS-IS level-1, L2 - IS-IS level-2 ia - IS-IS inter area, su - IS-IS summary null, * - candidate default U - per-user static route, o - ODR, L - local, G - DAGR, l - LISP A - access/subscriber, a - Application route M - mobile route, r - RPL, t - Traffic Engineering, (!) - FRR Backup path Gateway of last resort is not set C fd00:100:1::/64 is directly connected, 00:23:23, GigabitEthernet0/0/0/2 L fd00:100:1::ffff/128 is directly connected, 00:23:23, GigabitEthernet0/0/0/2 B fd00:100:2::/64 [200/0] via fd00:ffff::8 (nexthop in vrf default), 00:21:23 Juniper BGPネイバーの確認 user@v6-rt02> show bgp summary Threading mode: BGP I/O Default eBGP mode: advertise - accept, receive - accept Groups: 1 Peers: 8 Down peers: 0 Table Tot Paths Act Paths Suppressed History Damp State Pending bgp.l3vpn-inet6.0 5 5 0 0 0 0 Peer AS InPkt OutPkt OutQ Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped... fd00:ffff::1 65000 52 55 0 0 24:15 Establ bgp.l3vpn-inet6.0: 1/1/1/0 100.inet6.0: 1/1/1/0 fd00:ffff::3 65000 57 59 0 0 26:18 Establ bgp.l3vpn-inet6.0: 1/1/1/0 300.inet6.0: 1/1/1/0 fd00:ffff::4 65000 50 55 0 0 24:18 Establ bgp.l3vpn-inet6.0: 0/0/0/0 fd00:ffff::5 65000 60 59 0 0 26:25 Establ bgp.l3vpn-inet6.0: 0/0/0/0 fd00:ffff::6 65000 56 59 0 0 26:16 Establ bgp.l3vpn-inet6.0: 0/0/0/0 fd00:ffff::7 65000 52 55 0 0 24:17 Establ bgp.l3vpn-inet6.0: 1/1/1/0 300.inet6.0: 1/1/1/0 fd00:ffff::8 65000 61 59 0 0 26:12 Establ bgp.l3vpn-inet6.0: 1/1/1/0 100.inet6.0: 1/1/1/0 fd00:ffff::9 65000 57 59 0 0 26:17 Establ bgp.l3vpn-inet6.0: 1/1/1/0 200.inet6.0: 1/1/1/0 VRFの経路確認 user@v6-rt02> show route table 200.inet6.0 200.inet6.0: 5 destinations, 5 routes (5 active, 0 holddown, 0 hidden) + = Active Route, - = Last Active, * = Both fd00:200:1::/64 *[Direct/0] 00:27:19 > via ge-0/0/3.0 fd00:200:1::ffff/128 *[Local/0] 00:27:19 Local via ge-0/0/3.0 fd00:200:2::/64 *[BGP/170] 00:24:54, localpref 100, from fd00:ffff::9 AS path: I, validation-state: unverified > to fe80::20c:29ff:fe3e:f7fc via ge-0/0/0.0, Push 524286, Push 16009, Push 16005(top) fe80::20c:29ff:fe57:ca87/128 *[Local/0] 00:27:19 Local via ge-0/0/3.0 ff02::2/128 *[INET6/0] 00:28:50 MultiRecv Nokia BGPネイバーの確認 [/] A:user@v6-rt03# show router bgp summary <snip.> =============================================================================== BGP Summary =============================================================================== Legend : D - Dynamic Neighbor =============================================================================== Neighbor Description AS PktRcvd InQ Up/Down State|Rcv/Act/Sent (Addr Family) PktSent OutQ ------------------------------------------------------------------------------- fd00:ffff::1 65000 31 0 00h14m08s 0/0/1 (VpnIPv6) 33 0 fd00:ffff::2 65000 39 0 00h16m12s 0/0/1 (VpnIPv6) 38 0 fd00:ffff::4 65000 31 0 00h14m14s 0/0/1 (VpnIPv6) 33 0 fd00:ffff::5 65000 38 0 00h16m12s 0/0/1 (VpnIPv6) 37 0 fd00:ffff::6 65000 39 0 00h17m33s 0/0/1 (VpnIPv6) 40 0 fd00:ffff::7 65000 32 0 00h14m07s 1/1/1 (VpnIPv6) 33 0 fd00:ffff::8 65000 40 0 00h16m34s 0/0/1 (VpnIPv6) 38 0 fd00:ffff::9 65000 40 0 00h17m33s 0/0/1 (VpnIPv6) 40 0 ------------------------------------------------------------------------------- VRFの経路確認 [/] A:user@v6-rt03# show router service-name "300" route-table ipv6 =============================================================================== IPv6 Route Table (Service: 300) =============================================================================== Dest Prefix[Flags] Type Proto Age Pref Next Hop[Interface Name] Metric ------------------------------------------------------------------------------- fd00:300:1::/64 Local Local 00h21m21s 0 if-to-cust01 0 fd00:300:2::/64 Remote BGP VPN 00h16m08s 170 fd00:ffff::7 (tunneled:SR-ISIS:524298) 30 ------------------------------------------------------------------------------- No. of Routes: 2 Flags: n = Number of times nexthop is repeated B = BGP backup route available L = LFA nexthop available S = Sticky ECMP requested =============================================================================== tracerouteを用いた動作確認とSR-MPLS TE(Traffic Engneering)の設定について 各ルータでtracerouteコマンドを用いて想定した経路でパケットが転送されていることを確認します。 ここではrt07 (Cisco) とrt03 (Nokia) でのping/traceroute例を示します。 RP/0/RP0/CPU0:v6-rt07#traceroute fd00:300:1::ffff vrf 300 Thu Apr 13 09:54:05.712 UTC Type escape sequence to abort. Tracing the route to fd00:300:1::ffff 1 fd00:ffff::5 [MPLS: Labels 16003/524286 Exp 0] 4 msec 5 msec 12 msec 2 fd00:300:1::ffff 3 msec 12 msec 3 msec [/admin] A:user@v6-rt03# traceroute fd00:300:2::ffff router-instance "300" detail probe-count 1 traceroute to fd00:300:2::ffff, 30 hops max, 60 byte packets 1 1 fd00:300:2::ffff (fd00:300:2::ffff) 2.91 ms 以降は各PEルーターでSR Policyを設定し、広告する各VRFごとにColor Extended Communityを付加します。 そして、対向のPEルーターで受け取った経路にColorが付加されていることと対向の顧客への経路にSR Policyが適用されていることを確認します。 今回の検証では、機器の実装状況等の理由からIPv6 UnderlayでのSR Policyの動作確認はできませんでした。 検証例と成果 今回のインターンシップでは、動作確認のためこのような表を作成し検証を実施しました。 この体験記では、動作に成功した内容のうち、一部を紹介させて頂きました。 SR-MPLSのSID学習については各ベンダーで概ね動作し、学習したSIDを利用しての相互接続や通信にも成功しました。 またIPv6 Underlayを用いたSR-MPLS網の構築では、ルーターに自動的に設定されるリンクローカルアドレスを用いてIS-ISのルーティングを行うことができ、IPv4 Underlayと比較して効率的な構築が可能でした。 一方、今回の検証ではIPv6 Single/Dual-Stackの双方において、TEの設定を投入した際に想定通りの動作をしない事象が見られました。 これらの動作結果については、検証中の部分もあり実際の結果の掲載は控えますが、動かなかった場合の詳細な症状やメッセージなどをまとめることで、その後のメーカーへの問合せにつなげることができました。 インターンシップの感想 インターンシップに参加する前は、SR-MPLSという技術について全く知識がなかったため、業務を無事に進められるか非常に不安でした。 しかし、インターンシップではトレーナーの方々のサポートを受けながら、問題に直面しつつも楽しく検証作業を進めることができました。 SR-MPLS IPv6 Underlayを検証する中で、前提となるSR-MPLSの動作など、技術に関する知識や理解を深めておくことの重要性を感じました。 SRやその周辺技術については今後も継続して学習を続けたいと考えています。 業務以外の部分では、オフィスの見学やMTGへの参加、交流会や懇親会などを通じて、受け入れていただいたチームの雰囲気や別のチームの業務内容などを知るとともに会社全体のイメージを掴むことが出来ました。 最後にはなりますが、2週間のインターンシップで数々の貴重な経験をさせていただき、心から感謝しています。 竹中さん、三島さん、木村さんありがとうございました。 トレーナーからのコメント トレーナーの三島です。2週間のインターンシップお疲れ様でした! インターンシップで検証した、IPv6 Underlay SR-MPLSの相互接続は、我々を含めおそらく業界でも誰も試したことがない難易度の高いテーマでした。 その中でも、SRに関する事前学習や検証項目の整理や環境構築から動作結果をまとめたレポートの作成、ベンダへの問い合わせ項目の作成まで検証の一連の流れを一人称で進めていただけたこと、とても嬉しく思っております。 IPv6 Underlay SR-MPLSは、Plug-and-Playなネットワーク構築やSRv6 Migrationなどの固有の利点をもった面白い技術です。 大平さんに検証していただいたことで、各社の実装状況や、IPv6 Underlayの持つ利点が明確になり、SRv6 Migration等に向けた下地を得ることができました! 検証いただいた内容を基に我々も検証を進め、今後のネットワーク作りに役立てていきたいと思います。 このインターンシップで得た知見が、今後の大平さんの研究活動に役立つことを祈っています。 改めて、インターンシップへの参加とご活躍ありがとうございました!
アバター
はじめに こんにちは!イノベーションセンターテクノロジー部門 GPU-K8s Projectに二週間、インターン参加させていただいた石本直也と申します。 大学院では、WebAssemblyに関するObservabilityについて研究をしています。 今回の職場体験型インターンシップでは「モダンなICTインフラを支える技術の検証・開発(クラウド, コンテナ or データ基盤領域)」をテーマとして以下のことに取り組みました。 GPU ServerのWorker Nodeとしての設定(cuda,containerd,NVIDIA Container Toolkit) K8sクラスタへのGPU Worker Node追加 Ansibleでのセッティングの自動化 参考:職場体験型インターンシップとは? ドコモが仕掛ける数多くの事業の中から厳選したポストにて、実務を体験していただきます。 業務体験を通じて通信/ICT業界の仕事が理解できるだけでなく、皆さん自身の成長を実感できるインターンシップとなっています。 現場受け入れ型インターンシップコース | インターンシップ情報 | NTTドコモ新卒採用 インターン先 GPU-K8s Projectについて NTT ComのAIインフラ分野を牽引するテックリード的なプロジェクトで、ストレージ、GPUといったハードウェアからAIエンジニアが使うKubeflowといったAIプラットフォームOSSまで管理しています。 所属人数は10名以下ですが、扱う領域は広く運用効率化のためにK8s OperatorやKustomizeなどのOSSやAzure Arcなどのベンダー製品まで幅広くを検証しています。 以下の図は、社内向けAI基盤を構成している技術スタックを示した図です。 職場環境 フルリモートが主体の働き方を実現しているチームです。主なコミュニケーションツールとして、Slack, Google Meet, NeWorkを使用し、特にNeWorkの活用によってオフィスと遜色ない、場合によっては対面以上の働き方だと感じました。 その証拠に、函館からのリモートインターンシップでも二週間何も不自由なく参加でき、素直にすごいと感じました。 体験内容 一社員としてプロジェクトに配属され、実際に行われている業務内容を割り当てていただき社員の方のサポートの元で開発しました。その際、割り当てとなる業務内容に関しては、初日に自身の技術面や希望を話す機会を設けられそこで決定する流れでした。 私がAIインフラに関するGPUとK8sに関することがやりたいとお伝えしたところ、以下の2つを提示いただきました。その中で1番を選択し、GPU-K8s基盤へのworker node追加・その自動化に関するインターンシップを行いました。 [インフラ構築と自動化]:GPU-K8s基盤へのworker node追加・その自動化 [Go言語でのソフトウェア開発]:チームで内製開発中のK8s Operator の開発に参加 業務内容 GPU Worker Nodeの構築に関する調査、構築手順のドキュメント化 今回、Worker Nodeを追加するK8sクラスタは、実際にAIエンジニア向けに提供されているプロダクション基盤に追加する業務で、バグ等を踏み、業務使用されているK8sクラスタを壊す危険性がありました。 その際の影響を最小限に抑えるため、NVIDIA公式のインストールサイトや、Kubernetes.io docsを参考に一旦作業手順をNotionにまとめ、社員の方にご確認をいただき構築作業を開始しました。 GPU ServerのWorker Nodeとしての設定(cuda,containerd,NVIDIA Container Toolkit) Notionにまとめた作業手順を参考に、設定するサーバにsshし構築しました。 まず、CUDAドライバーのインストール、Containerdのインストール、コンテナ上からGPU利用を可能とするNVIDIA Container Toolkitのインストールを行いました。 最後にサーバ上で、Contaienrdのコマンドを用いてコンテナ上からGPUが見えていることをテストしました。 ctr image pull docker.io/nvidia/cuda:11.6.2-base-ubuntu20.04 K8sクラスタへのGPU Worker Node追加 設定したサーバをK8sクラスタへ追加するために、Kubeadm、Kubelet、Kubectlをインストールしました。 追加するK8sクラスタにおいて、Control Planeでのトークン作成 kubeadm token create --print-join-command を行いました。そのトークンをもとに kubeadm join を実行し、GPU Worker NodeをK8sクラスタへの追加を完了しました。 画像はメンテナンス中のもの Ansibleでのセッティングの自動化 次世代のH100 GPUが登場する中で、今後同様の設定を複数回行うことが予想されます。その構築や運用に関わる負荷軽減のために構築手順のAnsible化を行いました。 インターン最終日までにAnsible によりKubeadm joinを除くGPU Worker Nodeに関する設定のコード化ができました。 その際の自身のAnsible知識レベルは、OSS等でAnsibleのスクリプトは実行したことがあるものの、スクリプトを書いたことはない状態でした。そのためGCPで検証用VMを立てる権限をいただき、業務時間中に実際にスクリプトを書きながら学習しました。 以下の図が、Ansibleを実行した際の出力結果となります。 一部shellスクリプトによる実行を挟んでいるため、全てokでなくchangeになるものが発生しているのですが、冪等性(何回設定スクリプトを実行しても同じ結果となる)は担保できました。 学んだこと、感じたこと 私がこのインターンシップを経験して、学びその過程で感じたことは、大きく分けて3つありました。 基盤運用を効率化する技術について GPUが乗った状態の素のUbuntuOSに対して、GPUやK8sに関する設定を実施し、Ansibleによる設定の自動化までを経験したことで、実際に触った技術以外の運用技術もその役割や利用するメリットを理解できました。 Ansibleに関してはチームリポジトリへのプルリクエスト段階で、Ansibleのご経験がある社員の方からコードレビューをいただきました。自分では気づけない我流かつ荒削りになっていた部分や、よりわかりやすいディレクトリ構造や変数の使い方を学べとても良い貴重な経験になりました。 以下の図のように、運用効率化技術(AnsibleやK8s Operator)のAI基盤における使い分けについて実際に検証を通して学びました。 Ansible:Linux、K8s K8s Operator:K8s、OSS on K8s(e.g Prometheus、Kubeflow) AI処理に関する専用ハードウェアの重要性について DGX A100サーバを用いて検証する中で、 nvidia-smi で見た消費電力がGeForceシリーズと比べて大差がないことに疑問を感じました。そこで検証に使用しているnodeのGPU性能について知るために、NVIDIA製GPUのベンチマークについて調べてみることしました。 YouTubeの NVIDIA A100とRTX 3090のベンチマーク動画が大変参考になり、特に機械学習(ResNet50)に関する計算において、エンタープライズGPUとコンシューマGPU間で目に見えてわかる電力効率差があることにはかなり驚きました。 このことからPreferred Networks社が、MN-Coreと呼ばれる独自のハードウェアアクセラレータを開発し、計算機クラスタを構築する意図について理解できました。 参考:NVIDIA REFUSED To Send Us This - NVIDIA A100 NVIDIA A100とRTX3090のベンチマーク評価 参考:MN-Core 深層学習の学習フェーズに最適化した専用ハードウェア その他 (Sphinxを用いてネットワーク図の作成) 大学プロジェクト活動でのドキュメントが更新されてなくて苦しんだ経験があり、それの打開策になると感じました。 スライド等で図を作成するのではなくSphinxでアーキテクチャ図を作成することで保守性をあげることができると思うので、ぜひ取り入れていきたいです。 参考:Sphinx 図を含めたドキュメントをコード管理を可能とするツール 今後取り組みたいこと・学習指針 AIモデルとハードウェア処理の関係性調査 エンタープライズGPUとコンシューマGPUでは明らかに計算特性が異なることを知り、これと機械学習モデルの関係性が気になりました。機械学習モデルの推論や学習においてモデル内部の計算処理等を追うことでの適切なハードウェアを選択できるようになりたいです。 ソフトウェアレベルでのAI処理の効率化技術調査 これまで大規模言語モデルでは、そのモデルサイズと重みの大きさからA100やA6000のような大容量のVRAMを積んだGPUが必須とされてきました。しかしFlexGenの登場によって、コンシューマGPU(2023年4月時点で多くとも24GBのVRAMを搭載)でも大規模モデルを動かすことが可能となりました。今後上記を可能とするモデル処理に関するアーキテクチャについても調査し、ハードウェアとソフトウェアの両面からAI処理を効率化を行えるエンジニアになれるよう頑張ります。 参考:FlexGen 限られた GPU メモリで大規模な言語モデルを実行するための高スループット生成エンジン AI処理に関するハードウェアアクセラレータの調査 Preferred Networks社のMN-Core以外にも、SONYのIMX500といったCMOSセンサにAI推論機能を搭載したものが開発されています。ハードウェアアクセラレータはエッジAIの実現・普及に向けて欠かせない技術だと考えており、その検証も機会があればやっていきたいです。 参考:IMX500 世界初のAI処理機能を搭載したイメージセンサー 最後に 今回のインターンシップでは、実際にGPU基盤構築と自動化を通して実際の GPU運用効率化手法や更なるインフラ系の技術分野とその運用技術について知り、専門性を高めることができました。また、共同運営されているハイブリッドクラウドチームの方から、検証中の技術に関してお聞きし、OSSだけでなくAzure Arcといったより大規模な管理手法の理解も深まりました。 個人では触ることができない技術群で、そんな貴重機会をいただけたGPU-K8s Projectの皆さんに感謝でいっぱいです。 紀本さん、他にも業務を抱えている中でメンターとして二週間付きっきりでサポートしていただきありがとうございます!とても気さくに対応していただき、こんなこと聞いていいんだろうかと悩むことなく業務の体験・技術検証に集中できました。 GPU-K8s Projectの皆さん、上長としてサポートしてくださった松田さん、メンターとしてサポートしていただいた紀本さん、二週間本当にありがとうございました。
アバター
はじめに こんにちは!イノベーションセンター テレプレゼンスPJの渋谷です。 最近は日に日に暖かくなり,人々も花粉も舞い踊る気候になってきました。 ついこの間までの寒さが嘘のようです。 寒さといえば,そう,コタツ。 皆さんも「リモコンを取りたいのに手が届かない!だけどコタツからは出たくない!」「コタツが自分と一緒に動いてくれたら...」と,一度は考えたことがあるのではないでしょうか? そんな怠惰な願望を叶えるために,ロボティクスノウハウと余った時間と予算をフル活用し,今春,遂に「走るコタツ」が完成しました!! 今回は既に季節外れとなってしまった走るコタツの制作記録をご紹介します。 設計 はじめに,走るコタツを実現するには3つの要素があると考えました。 コタツと人を載せられるパワーを持つ駆動装置付きの台車 直感的な操作が可能な制御プログラム コードレスなコタツ これらを満たすソフト・ハードを細かく設計していきます。 台車 走るコタツには,人が乗るための強度とモーターで動かすための軽さの両方を備えた台車が必要です。そこで,軽くて強度の高いアルミフレームと木材を材料に選びました。 材料の特性を考慮して,効率的に人の体重を支えられる構造で設計されています。 駆動系 ロボットの駆動機構には,車輪やキャタピラ,多足など様々な方法がありますが,狭い屋内で重い荷重を運搬することを考えるとオムニホイールやメカナムホイールを用いた方式が適しています。 オムニホイールやメカナムホイールとは,空転する小さな筒がいくつも付いた特殊な構造の車輪です。各車輪の回転数を制御することで,前後に加え,斜め・真横への移動やその場で回転する動作ができます。また,自動車のようなタイヤをステアリングする方式と比べると地面の凹凸や砂・砂利に弱く,屋内などの平らな床面上に使用が限られます。 また,オムニホイールとメカナムホイールを比較すると,前者は4方向の特性が均一で回転時に車輪と路面の間にねじれが生じないという利点に対し,後者は前後方向の移動の効率・走破性に特化した性質を持ちます。 「走るコタツ」では,基本的には前後移動がメインとなることに加え,ノウハウの蓄積を兼ねて新たにメカナムホイールを利用することにしました。 また,人と台車の合計重量を駆動させるため,モーターもかなり強力なものが必要です。 今回は,100Wの大出力モーターを使用して,大きな負荷も駆動できる構成にしています。 駆動系設計の詳細 速歩き程度のスピードで走らせるため,以下のスペックのモーター・車輪を選定し最高時速5.4[km/h]で走行できるように設計しました。 モーター最大回転数4000[rpm],最大トルク0.319[N・m] ギヤ比1:30 車輪直径20.3[cm] 床面の転がり摩擦係数 を0.03,全重量 を120[kg]とすると,静止摩擦力を上回るために必要なトルクは となり,最大出力トルクの0.319[N・m]のほうが十分大きいため駆動可能です。 配線 モーターを駆動するには,バッテリー,モータードライバ回路,そして制御を行うPCが必要となります。 モーターのドライバ回路にはシリアル通信で命令できるI/Fが用意されているため,回路が非常にシンプルです。 実際には上記の回路に加え,電源スイッチや非常停止ボタン,PC電源用の降圧コンバーターやコントローラーが取り付けられています。 制御系 さて,これまでハードウェアについて解説してきましたが,人がロボットを動かすには操縦するコントローラーとその信号を受け取ってモーターに命令を出す制御プログラムが必要になります。 そこで,ジョイスティックを使うことでロボットのように操縦できるようなUIにしました。 走るコタツはジョイスティックを倒した方向に走行するだけなので,コンピューターにはそこまで大きな処理能力は求められません。 今回はJetsonを制御用コンピューターに使用しました(それでも十分すぎますが笑)。 このロボットでは,ROS on Dockerで動作環境を整備しています。 こうすることで突然PCが壊れてしまっても,簡単に同じ構成を復旧できます(ちなみに, 普段の業務でも作業効率向上のために同様なスキームを使っています)。 ROSは,ノードと呼ばれる単体のプログラムを組み合わせて大規模なシステムを構築するという構想でつくられています。 今回のロボットでは操作を受け取ってモーターを駆動するだけなので,1. 操作の入力,2. データの加工,3. ハードウェアの駆動の3つのノードで構成されています。 よく使われる機能が様々なライブラリとして公開されているのもROSのいいところで,ジョイスティックのデータを取得するプログラムが数行のコマンドでインストールできました。 また,ロボット本体の速度を各車輪の回転数に変換してモーターを駆動するノードを自作しました。 メカナムホイールで走行するロボットの順運動学,逆運動学を計算すると以下の式になったため,この式に基づいてそれぞれのモーターの回転速度を計算するプログラムを記述しました。 順運動学 逆運動学 , , はロボットのxy及び回転速度, ~ は車輪の回転速度, は車輪半径, , はトラック幅とホイールベースを表します。 軸,回転の方向は下記画像のように定義しています。 速度制御 これでジョイスティックから取得した値をメカナムホイール制御ノードに流せばロボットが動かせる状態になりました。しかし,このままではジョイスティックの角度がそのまま速度指令値となり,動き出しや停止時に大きな衝撃が加わって乗り心地が悪くなってしまいます。 そこで,入力デバイスとモーター制御モジュールの間に速度制御モジュールを挿入して,なめらかに加減速するためのフィードフォワード制御を行いました。 乗り心地を左右するのはズバリ加速度です。例えば,車のアクセルを突然踏むのと徐々に踏むのとでは,最終的な速度が同じでも後者のほうが衝撃が小さいということが想像できると思います。 つまり,ロボットを停止状態から(速度ではなく)加速度を徐々に大きくする必要があります。 これを工学的に実現するために,2次の伝達関数を設計しました。 このブロック図を簡単に説明すると,中央の と∫(インテグラル)のループで加速度aを徐々に変化するようにしており,外側のループで命令速度 が入力速度 に追従するようになっています。 この と を調整することで,「徐々に」の度合いを決めていきます。 まず,速度指令が入力されてから最大速度になるまでの時間の逆数がおおよそ になります。 また, は の10倍程度※の値で,小さいほど動き出しがゆっくりになりますが, の4倍より小さくすると共振が生じてしまいます。 最終的な値は,組み立て後に動かしながらいくつかの値で試し, , としました。 この制御ブロックをROSのノードとして実装すればソフトの完成です。 制御系設計の詳細 伝達関数,ステップ応答を計算した結果がこちらになります。 伝達関数 ステップ応答 , with , , ステップ応答の時間関数 今回の設計値を代入し,時間応答をプロットするとこんなグラフになります。 比較:1次の伝達関数で同じように設計した場合,速度を上げた瞬間に加速度が急激に変化してしまいます。 あれこれ難しいことやってるけど実際に効果あるの?と気になると思いますが,実際の乗り心地は全然違います! コーヒーを乗せて比較した映像がこちら(左:2次の伝達関数を使用(工夫した方),右:1次の伝達関数を使用)。 どちらも同じ入力を与えているのですが,制御を工夫した方は液面の揺れがだいぶ抑えられています。 コタツ 最後に,この走るコタツのアイデンティティとなるコタツを用意します。 会社にコタツがあるわけ...と思いますよね?実はあるんです!私もチームに入ったときびっくりしました。 どうしてコタツがあるかは定かではありませんが,休憩時間にほっこりできるのはいいものです。 このコタツを台車に載せれば走るコタツが完全体になります!! もちろん電源も乗せているので,温かいコタツに入りながら移動できます笑 組み立て 設計ができたので,あとはアルミフレームなどのパーツや回路部品を発注して組み立てていきます。 そのときの作業風景がこちらになります。普段から本チームのメンバーはこんなスペースで仕事をしています。 余談ですが,この作業風景は実はキャタピラ駆動の「走るコタツVer.1」を作成したときの映像です。キャタピラでは人を乗せてターンできなかったため,今回「Ver.2」としてメカナムホイールで作り直しています。 回路はこの画像のようにロボットの内部に配線されています。 数百ワットの出力が出せるバッテリーを搭載しており,万が一ショートしたら大変なことになるため丁寧に配線しました。 各部品は3Dプリンタで印刷したジグでアルミフレームの柱に固定されていて,後の整備のために真ん中の天板を外して回路にアクセスできるようになっています。 部品を取り付けるスペースが若干足りなかったので,一番軽いJetsonが裏面送りになりました笑 最後に,ジョイスティックと非常停止スイッチ,そしてみかんを設置すれば完成です!! 実際に乗ってみた 一言でいうとめっちゃ楽しいです! メカナムホイールならではの真横移動はとてもシュールです笑 その場で回転もできるため,思ったより狭い通路も通ることができます。 これで来年の冬は,1日中コタツから出ることなく過ごせそうです!! 春夏秋でも,動画の後半部分のようにコタツを外して「走るスノコベッド」としても利用できます笑 転職・就職をお考えの皆様へ 本記事では,我々の夢を詰め込んだ「走るコタツ」プロジェクトについて,設計から組み立て,試乗までをご紹介しました。NTT Comでロボティクスの研究開発をしているチームがあることを知らなかった人も多いのでは無いでしょうか? ものづくりやロボティクスの楽しさが少しでも伝わっていたら嬉しいです。 私が所属するテレプレゼンスPJでは,ソフトウェアだけでは届かない物理空間をスマート化することを目的に,ロボットと通信をかけ合わせた領域の技術開発とビジネス展開に取り組んでいます。 中途採用を通年募集中で,2024年度から新卒採用を再開する予定です(※インターンの受け入れも計画しています)。興味のある方,通信×ロボティクスをやってみたい方はぜひご応募ください! 中途採用リンク: https://hrmos.co/pages/nttcom0033/jobs/1821073673089876026 新卒採用リンク: https://information.nttdocomo-fresh.jp/information/requirement 免責事項 本プロジェクトではしっかり安全対策した上でスタッフ個人の責任の元で制作・試乗しています。 この記事を見て作成したロボット等で発生したいかなる損害の保証もできませんので,このような乗り物を作成する際はパーツの注意事項等をきちんと把握した上で十分に事故等にお気をつけください。
アバター
はじめに 皆様こんにちは。イノベーションセンター SkyWay DevOps プロジェクト所属の @sublimer です。 私達のチームは、1月31日に新しいSkyWayをリリースしました。 skyway.ntt.com 新しいSkyWayでは、公式サイトや、アプリケーションを管理するコンソールも全く新しいものとなっています。 今回は、新しいSkyWayの公式サイトやコンソールの死活監視に利用している、Google Cloudの「稼働時間チェック」機能についてご紹介します。 稼働時間チェックとは 稼働時間チェックは、Google CloudのCloud Monitoringが提供している機能の1つです。 予め設定したエンドポイントに対して、HTTP・HTTPS・TCPのいずれかのプロトコルで定期的にリクエストを送り、そのレスポンスがアラート条件を満たした場合に通知してくれるサービスです。 監視対象のエンドポイントはURLやIPアドレスでも設定できるので、Google Cloud以外の環境に対して監視したい場合でも活用できます。 稼働時間チェックの特徴として、アラート条件を柔軟に設定できる点が挙げられます。 単純にステータスコード等をチェックするだけでなく、レスポンスのJSONの特定のキーが予め指定された値になっているか等の条件も設定できます。 また、比較的大きめの無料枠が提供されている点も特徴として挙げられます。 稼働時間チェックは、1,000回の実行につき$0.30という料金設定ですが、Google Cloudのプロジェクト1つあたり100万回の実行まで毎月無料で利用できます。 cloud.google.com 3つのリージョンから毎分チェックを実行した場合の1ヶ月間の実行回数は、 3リージョン x 60分 x 24時間 x 31日 = 133,920回なので、だいたい7個のエンドポイントに対して無料でチェックができます。 稼働時間チェックの設定方法 監視対象の設定 稼働時間チェックは、以下のURLから設定できます。 https://console.cloud.google.com/monitoring/uptime/create 基本的には入力欄を埋めていくだけで設定が完了するのですが、一点だけ注意点があります。 設定画面の「More target options」をクリックするとより詳細な設定画面が表示されるのですが、「Regions」の設定はデフォルトで「グローバル」となっています。 Regionsは、最低3箇所、最大6箇所の範囲で自由に決めることができます。 実行回数はリージョン数との掛け算で決まるため、無料枠の範囲内に収めるためには3リージョンを設定するのが望ましいです。 今回は、以下の画像のように、アジア太平洋(シンガポール)、ヨーロッパ(ベルギー)、米国(オレゴン)を設定しました。 レスポンスの検証の設定 レスポンスの検証の設定画面は以下の画像のようになっています。 ここでは、以下の項目を設定しています。 タイムアウト時間は10秒 レスポンスのJSONの status というキーの値が "OK" と完全に一致すること レスポンスのステータスコードが200であること したがって、10秒以内にステータスコード200で、 status というキーの値が "OK" のJSONがレスポンスとして返されていれば正常と判定されます。 コンテンツマッチの設定は、上記のJSON Pathによるものの他、特定の文字列が含まれているかどうかや正規表現によるチェックもできます。 なお、JSON Pathで指定したキーについて文字列で値が返される場合は、Response Contentの設定は、ダブルクオーテーション ( " ) を含めて設定する必要があります。 通知先の設定 「アラートと通知」の設定画面では、アラートの通知先を設定できます。 稼働時間チェックの通知先は、「通知チャンネル」として予め設定した通知先の中から選択できます。 通知チャンネルには、SlackやEmailの他、Cloud Pub/SubやPagerDutyを設定することもできます。 今回はSlackを通知先として設定することにしました。 ここまで設定できたら、最後に確認画面で「TEST」ボタンを押し、正しくレスポンスのチェックができているかを確認します。 アラートのカスタマイズ Slackの通知メッセージの変更 Slackに通知されるメッセージの内容は、以下のURLからアラートポリシーを開き、編集画面の「通知と名前」から変更できます。 https://console.cloud.google.com/monitoring/alerting 今回は、チャンネル全体へのメンションと、いくつかのラベルを表示させるため、以下のように設定しました。 <!channel> ${policy.display_name} Checker Location: ${metric.label.checker_location} Target Host: ${resource.label.host} Slackの @channel のメンションは、 <!channel> で設定できます。 メッセージ内で利用できる変数は、以下のドキュメントに記載されています。 cloud.google.com リクエスト送信元ごとのアラート条件を設定する アラートポリシーの設定画面で「時系列のグループ化の基準」に checker_location を追加すると、稼働時間チェックのリクエスト送信元ごとに条件判定ができるようになります。 動作確認 動作確認として、あるリージョンからのリクエストのみを受け付けないようにして、正常に通知が行われるかを確かめます。 今回、チェック対象は私の自宅にあるWebサーバーとしたので、Webサーバーのnginxで ASIA_PACIFIC からのリクエストを拒否するように設定します。 稼働時間チェックの送信元IPアドレスの一覧は、以下のURLの「稼働時間チェックを作成」の左隣にある下向き矢印をクリックするとダウンロードできます。 https://console.cloud.google.com/monitoring/uptime テキストファイルとしてダウンロードされますが、中身はJSONのファイルなので、jqを使ってシュッと ASIA_PACIFIC のIPアドレスだけ抜き出します。 cat uptime-source-ips.txt | jq -c '.[] | select(.region == "ASIA_PACIFIC").ipAddress' -r あとは、これらのアドレスをnginxの設定ファイルで以下のように追記すれば、 ASIA_PACIFIC からのリクエストのみを拒否する設定ができます。(IPアドレスは例示用のダミーの値です。) deny 203.0.113.1; deny 203.0.113.2; deny 203.0.113.3; deny 203.0.113.4; deny 203.0.113.5; deny 203.0.113.6; deny 203.0.113.7; deny 203.0.113.8; deny 203.0.113.9; allow all; nginxの設定変更後、少し経つと以下のようなアラート通知がSlackに送られるはずです。 おわりに 新しいSkyWayの公式サイトやコンソールの死活監視に利用している「稼働時間チェック」機能について、設定例も交えながら紹介しました。 今回、新しいSkyWayにおいて死活監視をどのように行うかを検討する上で様々なSaaSについて比較検討を行いましたが、チェックのリクエストを送る間隔の短さや監視設定の柔軟さという点で、稼働時間チェックが一番使いやすい印象を受けました。 シンプルな死活監視であれば比較的簡単に設定ができるので、とりあえずお手軽に死活監視をしたい場合は稼働時間チェック機能を使ってみてはいかがでしょうか? 稼働時間チェック機能には設定可能な項目がいろいろあるので、ある程度凝った監視をしたい場合でも役立つのではないかと思います。 稼働時間チェック機能でしっかり監視をしている新しいSkyWayを、ぜひ使ってみてください!! 参考サイト Create public uptime checks  |  Cloud Monitoring  |  Google Cloud List uptime-check server IP addresses  |  Cloud Monitoring  |  Google Cloud Using Markdown and variables in documentation templates  |  Cloud Monitoring  |  Google Cloud
アバター
はじめに こんにちは、イノベーションセンターの鈴ヶ嶺です。 普段はクラウドサービスをオンプレミス環境でも同様のUI/UXで使用を可能とするハイブリッドクラウド製品の技術検証をしています。 過去に我々はAWS Outposts ラックの検証内容を公開しました。 engineers.ntt.com 今回本記事では、新たに導入したAWS Outposts サーバーの仕様、導入方法、利用方法について徹底解説します。 AWS Outposts とは AWS Outpostsは、AWSのハイブリッドクラウド製品です。オンプレミス上に製品を設置してPublic AWSと同じような操作性でインフラストラクチャとサービスを作成できます。 主なユースケースは次の4つのケースが想定されます。 低レイテンシーコンピューティング 最寄りのパブリッククラウドサーバーでは通信遅延要件を満たさない場合、近距離地点にAWS Outpostsを設置することで低遅延な高速処理を実現できます。 データレジデンシー 規制や情報セキュリティ上の理由から特定の所在地に保存する必要があるデータ(例えば金融・ヘルスケア情報など)について、AWS Outpostsを利用することでデータの常駐場所の制御を可能とします。 移行とモダナイゼーション 移行が難しいオンプレミスのレガシーシステムについて、クラウドへの移行準備ができるまでのホスティング先として利用できます。AWSへの接続性を活用して、クラウド移行を計画し、完了できます。 ローカルデータ処理 ローカルでプライベート処理することでセキュリティ上のリスクを低減します。コストやネットワーク帯域幅の問題により従来では処理不可能なデータセットを処理可能です。 Public AWSとAWS Outpostsの関係を示した図が次のようになります。物理的には独立していますが、論理的にはVPCを共有しAWS Outposts上にSubnetが作成されるような形式となります。また、コントロールプレーンは既存のPublic AWSのコンソールやAPIから利用します。つまり各拠点にAWS環境が延伸されるようなものとして考えるとわかりやすいかと思います。 2023年3月時点ではAWS Outposts ファミリーには以前紹介した AWS Outposts ラック と今回新たに紹介する AWS Outposts サーバー の2つがメンバーとして存在しています。 AWS Outposts サーバーとは 引用(OGP): https://aws.amazon.com/jp/blogs/news/new-aws-outposts-servers-in-two-form-factors/ AWS Outposts サーバーはAWS Outposts ラックとは異なり、1Uもしくは2Uの専用サーバーをデータセンターなどのオンプレミス環境に設置して使用します。 今までの「ラック」は、データセンターなどへの持ち込みラックの設置準備やサーバーに比べると高い使用料金が課題でしたが、サーバ型はそれらの課題を解消したモデルになっていると思われます。 ラックとサーバの価格設定の詳細については以下の詳細を参照してください。 AWS Outposts サーバーの価格設定 AWS Outposts ラックの料金 以下がAWS Outposts サーバーでサポートされるサービス一覧です。 AWS Outposts サーバーでサポートされるサービス一覧 Amazon EC2 Amazon ECS AWS IoT Greengrass Amazon Sagemaker Edge Manager Amazon Virtual Private Cloud また、次のようにAWS Outposts ラックではサポートされていた機能が一部サーバーではサポートされていないためそれぞれの違いに注意が必要です。 AWS Outposts サーバーではサポートされていないサービス一覧 Amazon Elastic Kubernetes Service (EKS) Amazon Simple Storage Service (S3) Amazon Relational Database Service (RDS) Amazon Elasticache Amazon EMR フォームファクタ 次の表が、2023年3月時点で利用可能なサイズ一覧です。 プロセッサにはx86やArm/Graviton2などのアーキテクチャが選択可能です。特にArm/Graviton2については低消費電力が大きな特徴のため、電力制約の規制が厳しいエッジ環境への設置やカーボンニュートラルへの取り組みにおける活用が期待されます。 Outpost リソース ID ラックユニットの高さ EC2 容量 プロセッサ/アーキテクチャ vCPU メモリ ローカル NVMe SSD ストレージ ネットワークアップリンク 消費電力 重量 電源タイプ OR-STBKRBE 1U c6gd.16xlarge Graviton2 / Arm 64 128 GiB 3.8 TB 10 Gbps 0.8 kVA 13 kg AC OR-LMXAD41 2U c6id.16xlarge インテル Ice Lake/x86 64 128 GiB 3.8 TB 10 Gbps 1.5 kVA 16 kg AC OR-KOSKFSF 2U c6id.32xlarge インテル Ice Lake/x86 128 256 GiB 7.6 TB 10 Gbps 1.5 kVA 16 kg AC ネットワーク設計 AWS Outposts サーバーはPublic AWSと接続する管理系のリンクであるservice linkとローカル環境と接続するためのlocal network interface (LNI) linkがあります。次の図のように付属のQSFPブレイクアウトケーブルを利用しラベル1をLNI linkとして使用し、ラベル2をservice linkとして使用します。ラベル3-4のケーブルについては現時点では未使用となります。 引用: https://docs.aws.amazon.com/outposts/latest/server-userguide/install-server.html#install-network 次の図がネットワークの概要図です。 まずservice linkを用いてサーバーをアクティベートする必要があります。AWS Outposts サーバーには固定IPアドレスを設定する方法がないため、service linkにDHCPを用いてIPアドレスを設定します。その後、シリアル接続したコンソールから outposts.[設置region].amazonaws.com エンドポイントへの疎通確認やIAM認証情報を用いてAWSと接続することでアクティベートが完了します。 1 ローカル環境と接続されるLNI LinkはNetwork Interfaceに紐づく形で利用されます。利用するためには、まずAWS Outposts サーバー上のSubnetでLNIとして利用するNetwork Interface Indexを設定します。その後新たにNetwork Interfaceを作成し、該当のIndexとしてアタッチしてIPアドレスを設定することでローカル環境と接続できるようになります。実際の利用方法は後述するEC2の起動検証でも詳しく説明します。 引用: https://docs.aws.amazon.com/outposts/latest/server-userguide/local-network-interface.html EC2の起動検証 AWS CLIベースで次の一連の処理を行いEC2の起動を検証します。 VPCの作成 AWS Outposts Subnetの作成 SubnetのLNI有効化 EC2 Instanceの起動 user-dataによるLNI NetworkのIPアドレスとデフォルトルートの設定 ローカル環境との疎通確認 # 事前設定 ## AWS Outposts サーバーARN OUTPOST_ARN = ' arn:aws:outposts:ap-northeast-1:XXXXXXXXXXXX:outpost/op-XXXXXXXXXXXXXXXX ' ## sshのためのkeypair KEYPAIR = ' XXXXX ' ## AWS Outposts サーバーのLNI Network(ローカル環境のNetwork)を192.168.10.0/24と想定 OUTPOST_LNI_EC2_IP = ' 192.168.10.2 ' OUTPOST_LNI_SUBNET = ' 24 ' OUTPOST_LNI_GATEWAY_IP = ' 192.168.10.1 ' # 1. VPCの作成 VPC_ID = $( aws ec2 create-vpc --cidr-block 10 . 0 . 0 . 0 / 16 \ --tag-specifications " ResourceType=vpc, Tags=[{Key=Name,Value=Outposts-Server-VPC}] " \ --query Vpc.VpcId --output text ) # 2. AWS Outposts Subnetの作成 SUBNET_ID = $( aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10 . 0 . 1 . 0 / 24 \ --availability-zone ap-northeast-1a --outpost-arn $OUTPOST_ARN \ --tag-specifications " ResourceType=subnet, Tags=[{Key=Name,Value=Outposts-Server-Subnet01}] " \ --query Subnet.SubnetId --output text ) # 3. SubnetのLNI有効化 aws ec2 modify-subnet-attribute --subnet-id $SUBNET_ID --enable-lni-at-device-index 1 # 4. EC2 Instanceの起動 ## Security Group作成 SG_ID = $( aws ec2 create-security-group --group-name Outposts-Server-SG \ --description " Default Security group " --vpc-id $VPC_ID \ --tag-specifications " ResourceType=security-group, Tags=[{Key=Name,Value=Outposts-Server-SG}] " \ --query GroupId --output text ) ## Ubuntu22.04の最新AMIを取得 UBUNTU_AMI = $( aws ec2 describe-images --owners 099720109477 \ --filters " Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64* " \ --query " reverse(sort_by(Images, &CreationDate))[0].ImageId " --output text ) ## 4.1 user-dataによるLNI NetworkのIPアドレスとデフォルトルートの設定 cat << EOF > user-data.txt #!/bin/bash set -xe # 固定IPの設定 ip addr add $OUTPOST_LNI_EC2_IP / $OUTPOST_LNI_SUBNET dev ens6 # デフォルトゲートウェイの設定 ip route add default via $OUTPOST_LNI_GATEWAY_IP EOF ## ENI作成 ENI_ID0 = $( aws ec2 create-network-interface --subnet-id $SUBNET_ID --groups $SG_ID \ --tag-specifications " ResourceType=network-interface, Tags=[{Key=Name,Value=Outposts-Server-ENI0}] " \ --query NetworkInterface.NetworkInterfaceId --output text ) ENI_ID1 = $( aws ec2 create-network-interface --subnet-id $SUBNET_ID --groups $SG_ID \ --tag-specifications " ResourceType=network-interface, Tags=[{Key=Name,Value=Outposts-Server-ENI1}] " \ --query NetworkInterface.NetworkInterfaceId --output text ) ## EC2 Instance作成 INSTANCE_ID = $( aws ec2 run-instances --image-id $UBUNTU_AMI --instance-type c6id.8xlarge \ --key-name $KEYPAIR --user-data file://user-data.txt \ --network-interfaces " [{ \" DeviceIndex \" :0, \" NetworkInterfaceId \" : \" $ENI_ID0 \" },{ \" DeviceIndex \" :1, \" NetworkInterfaceId \" : \" $ENI_ID1 \" }] " \ --tag-specifications " ResourceType=instance, Tags=[{Key=Name,Value=Outposts-Server-Instance}] " \ --query ' Instances[0].InstanceId ' --output text ) # 5. ローカル環境との疎通確認 ssh ubuntu@ $OUTPOST_LNI_EC2_IP 作成したEC2は次のスクリーンショットのようにAWS Management Consoleから確認できます。 EC2の画面からはPublic AWSかAWS Outposts サーバーのインスタンスかを判断できないためSubnetを確認します。 次のようにSubnetの詳細画面を見るとOutpost IDが紐づいていることが分かるため、AWS Outposts サーバーのインスタンスが正常に起動していることを確認しました。 また、LNIを有効化したネットワークインターフェイスはインターフェイスのタイプが「ローカルネットワークインターフェイス」と記述されていることが分かります。 ちなみに 5. ローカル環境との疎通確認 でSecurity Groupのインバウンド設定をしていないのになぜsshでEC2と疎通できるのか気になった方もいると思います。 これは以下のようにLNIはSecurity Groupを利用しない仕様のためです。 セキュリティグループとローカルネットワークインターフェイス 設計上、ローカルネットワークインターフェイスは VPC 内のセキュリティグループを使用しません。セキュリティグループは、インバウンドとアウトバウンドを制御します。VPC トラフィック。ローカルネットワークインターフェイスは、VPC にアタッチされていません。ローカルネットワークインターフェイスは、ローカルネットワークにアタッチされています。ローカルネットワークインターフェイスでインバウンドおよびアウトバウンドトラフィックを制御するには、他のオンプレミス機器でファイアウォールまたは同様の戦略を使用します。 引用: https://docs.aws.amazon.com/ja_jp/outposts/latest/userguide/how-servers-work.html まとめ 本記事では AWS Outpostsについて AWS Outposts サーバーについて EC2の起動検証 について記載しました。 AWS Outposts サーバーは省スペースや電力規制が厳しい状況においても設置可能な新たなAWSのハイブリッドクラウド製品です。AWS環境をエッジに延伸しクラウドと同様の開発体験で低遅延処理やセキュリティ課題を解決する場合にはこれを機に AWS Outposts サーバーの利用を検討してみてはいかかでしょうか? https://docs.aws.amazon.com/ja_jp/outposts/latest/userguide/install-server.html#authorize-3 ↩
アバター
はじめに こんにちは、NTTコミュニケーションズの現場受け入れ型インターンシップに参加した奥です。 現在は大学でLinuxマルウェアの動的解析について研究しています。 今回私は2023年2月6日から2月17日の2週間、イノベーションセンターのNetwork Analytics for Security(通称NA4Sec)プロジェクトに参加し、 Cobalt Strikeを悪用した攻撃事例の調査、またCobalt Strikeのペイロード配布に利用される攻撃インフラの発見・追跡を体験しました。 この記事では、インターンシップ参加の経緯やインターンシップ期間中に取り組んだ内容について紹介します。 インターンシップ参加の経緯 私がこのインターンシップに応募したのは、夏に他社のインターンシップに参加した際に、他の学生たちがNTTのインターンシップを推していた記憶があり、応募しておけばよかった..と悔やんでいたためです。 後に調べてみると、冬季インターンシップも2週間という期間にわたって現場での業務体験ができ、セキュリティ分野だけでも様々な内容のポストがあることを知り、充実した経験ができると確信を持って応募しました。 しかし、実際に選考を受けてみると「私はただものではない」エピソードがないことに悩まされたり、インフラ関係のポストに応募したにもかかわらず、ネットワークの知識が全然ないことに不安を覚えたりしました。 そんな時、一次面接でNA4Secプロジェクトリーダーの神田さんとお話しし、とても穏やかな雰囲気の中、プロジェクトやインターンシップで取り組む内容について詳しく聞けました。一次面接は人事の方が担当することが多いイメージでしたが、初めに現場の方とお話しし、インターンシップの具体的なイメージができたことは、選考を受ける中で大きなモチベーションとなりました。 NA4Secプロジェクトとは 私が参加したNA4Secプロジェクトとは、「NTTはインターネットを安心・安全にする社会的責務がある」を理念として、攻撃インフラの解明、撲滅を目指すプロジェクトです *1 。 Tier1 ISP(Internet Service Provider)であり、MSSP(Managed Security Service Provider)でもあるNTTの強みを活かし、攻撃インフラを追跡するとともに、解析のための新たな技術を開発しています。 インターンシップで取り組んだこと 2週間で体験したインターンシップの内容をまとめます。 1. Cobalt Strikeの理解 Cobalt Strikeについての知識がなかったため、そもそもCobalt Strikeがどういったもので、なぜ攻撃に使われるのかを理解することからスタートしました。 「 Cobalt Strike 」はサイバー攻撃を再現し、システムや組織のセキュリティを評価するために使用される商用のセキュリティツールです。 一方で、Cobalt Strikeはクラック版(不正利用のために改変されたもの)が出回ったことにより、悪意のある攻撃にも広く利用されています。 主な機能として、システム侵入後に対象システムに Cobalt Strike Beacon (以下、Beacon)と呼ばれるバックドアプログラムを埋め込み、稼働させることでBeaconを介してPowerShellスクリプトの実行や、スクリーンショットの記録、ファイルのダウンロードなどの強力な攻撃モジュールを提供します。また、ネットワーク設定を調整することで通信を秘匿したり、初期アクセスのためのフィッシングメールを作成する機能も備えています。 攻撃者から見た時のCobalt Strikeの魅力として、攻撃技術の洗練度や人的・金銭的リソースの豊富さに関わらず、迅速に攻撃を行えることが挙げられます。また、広く利用されているために、正規のペネトレーションテストとの判別や、攻撃グループの特定を困難にする効果を狙っているケースもあるかもしれません。 2. Cobalt Strikeを悪用した攻撃事例の調査 次に、Cobalt Strikeを悪用した過去の攻撃事例を整理し、 脅威インテリジェンス としてまとめました。 具体的には攻撃グループや攻撃の手口、どういった行動を誰に対して行っているのかといった観点で情報を整理し、そのパターンから活動の背景や動機などを考察しました。 これらの情報を活用することにより、攻撃側が有利なサイバーセキュリティにおいて、防御側は攻撃被害が発生する前により効果的と考えられる対抗手段を取って攻撃に備えることができます。 今回は、攻撃事例の情報を整理するために、 ダイヤモンドモデル というサイバー攻撃のパターン分析手法を用いました。ダイヤモンドモデルは、以下の4つの特徴でサイバー攻撃を定義づけます。 攻撃グループ(Adversary) 攻撃者に関する情報(呼称,アカウント,関連するグループなど) 被害者(Victim) 被害者に関する情報(組織名,システム名,IPアドレスなどのネットワーク資産,デバイス,アカウントなど) 攻撃基盤(Infrastructure) 攻撃に際して使われた基盤(サービスアカウント,IPアドレス,ドメイン名,メールアドレスなど) 能力(Capability) 具体的な攻撃手法やツール(マルウェアやその送信方法,悪用された脆弱性など) 実際に、Cobalt Strikeに関する直近25件の攻撃事例をダイヤモンドモデルに当てはめてみました。情報源としては、 Microsoft Defender Threat Intelligence が無償で得られる情報の範囲でも多くの攻撃事例に対するレポートをまとめてくれており、効率的な情報の収集に役立ちました。 図1 ダイヤモンドモデルに基づき作成した表の一部 調査の結果、Cobalt Strikeは国家のスパイや犯罪グループにも利用されており、大規模な攻撃に使われていることが分かりました。 また、そのような国家を背景とした犯罪グループは、政府機関や重要インフラを狙うことが多く、標的とされる国にも特徴が見られました。 侵入経路としては、フィッシングメールや侵害したWebサイト、または脆弱性を利用することで最初のペイロードをダウンロードさせ、それを使うことでCobalt Strike Beaconを標的システムに配布していました。その後は、Cobalt Strikeの機能を使うことで、権限を昇格したり、特徴的なマルウェアをダウンロードする場合が多く見られました。 3. Cobalt Strike C2サーバの探索 Cobalt Strikeを悪用する攻撃者はインターネット上にTeam Serverと呼ばれるサーバを公開し、Beaconからの通信を待ち受けています。 このようなサーバを一般的にC2(Command & Control)サーバと言います。 インターンシップ後半では、主にインターネットスキャナ系検索エンジンを用いてCobalt Strike C2サーバの発見に挑戦しました。 インターネットスキャナ系検索エンジン 世の中にはインターネット上を定期的にスキャンして得られたデータを検索可能にしている検索エンジンサービスがあります。 例えば Censys は、毎日40億以上のIPv4ホストやWebサイトを定期的にスキャンしており、インターネット上に接続されている機器の情報(OSやソフトウェアのバージョン、IPアドレス、AS、ドメイン名、SSL/TLSサーバ証明書に関する情報)等で検索できます。 今回はCensysの他に Shodan 、 ZoomEye を加えた3つの検索エンジンを用い、それらを比較しました。 検索クエリの作成 膨大なデータベースの中からCobalt Strike C2サーバを見つけるためには、その特徴に合わせて効果的に条件を指定する必要があります。 ここでは、 Censys を用いて検索する方法を紹介します。 SSL/TLSサーバ証明書 最も誤検知を抑えてCobalt Strike C2サーバを探す方法の1つは、SSL/TLSサーバ証明書がCobalt Strikeでよく使われる証明書と一致しているものに絞ることです。 攻撃者がそれらのSSL/TLSサーバ証明書を使用している場合は、この方法で発見できます。 services.certificate: { "64257fc0fac31c01a5ccd816c73ea86e639260da1604d04db869bb603c2886e6", "87f2085c32b6a2cc709b365f55873e207a9caa10bffecf2fd16d3cf9d94d390c" } OR services.tls.certificates.leaf_data.issuer.common_name: "Major Cobalt Strike" OR services.tls.certificates.leaf_data.subject.common_name: "Major Cobalt Strike" 1~3行目 SSL/TLSサーバ証明書のFinger Print 4~5行目 SSL/TLSサーバ証明書の発行元(issuer)と主体者(subject) 実際にこの条件でフィルターをかけたところ、583件のCobalt Strike C2サーバと見られるIPv4ホストを発見できました(3月15日時点)。 図2 SSL/TLSサーバ証明書で絞った検索結果 ポート番号とHTTPヘッダ SSL/TLSサーバ証明書に基づく探索は、確実である反面、カスタマイズされた証明書が使われている場合や、HTTPなどのSSL/TLSで暗号化を行わない通信プロトコルの場合に発見できなくなるという弱点があります。 Cobalt Strike C2サーバのポート番号やHTTPヘッダに見られる特徴を組み合わせることで、そのような場合であってもCobalt Strike C2サーバである可能性の高いIPv4ホストを見つけられます。 services.port: 50050 AND services.http.response.status_code: 404 AND services.http.response.headers.content_type: text/plain AND services.http.response.headers.content_length: 0 AND NOT services.http.response.headers.server 1行目 使用ポートの条件はCobalt Strikeのデフォルトポートである50050ポート 2~5行目 HTTPヘッダの内容 実際に、インターンシップ用に立てられたCobalt Strike C2サーバに対してスキャンしてみたところ、図3の赤枠内のHTTPヘッダを持つレスポンスが得られました。 これは一般的なCobalt Strike C2サーバのHTTPヘッダの特徴と一致しています。 図3 Cobalt Strike C2サーバからのレスポンス 実際にこの条件でフィルターをかけたところ、294件のサーバにヒットしました(3月15日時点)。 それぞれのIPv4ホストについて詳しく見てみると、多くがCobalt Strikeの証明書や自己署名証明書を利用しているなど、攻撃インフラの可能性が十分に考えられるものでした。 図2 ポート番号とHTTPヘッダで絞った検索結果 検体の解析によるC2の発見 Censysにはない機能として、ShodanやZoomEyeはスキャン対象となるサーバからBeaconを入手し、解析することによってCobalt Strike C2サーバかどうかを判定しています。また、以下のように解析結果を得ることも可能です。 Cobalt Strike Beaconの解析結果(Shodan) インターネットスキャナ系検索エンジンの比較 今回用いた3種類のインターネットスキャナ系検索エンジンについて、Cobalt Strike C2サーバを探索する際の長所と短所をまとめます。 情報源 pros cons Censys スキャンしているポートの数が多い(50050ポートの結果を得られる),スキャン頻度が高い Beaconの入手と解析はしていない Shodan Beaconの解析結果が得られる、スキャン頻度が比較的高い スキャンしているポートの数が少ない ZoomEye Beaconの解析結果が得られる スキャン頻度が低い、中国サーバのスキャン結果が表示されない インターンシップを振り返って サイバーセキュリティの最前線で長年戦ってきた方に、2週間の間マンツーマンで指導していただけたことは、本当に贅沢な経験だったと思います。 ネットワークの基礎的な構成技術から、研究で注目している新たな技術まで、幅広く深く触れることのできた密度の濃い2週間でした。 脅威を見定め、能動的に情報を収集して防御に活用する アクティブサイバーディフェンス という、今まで私の中になかった視点を得ることができたこと、 そのための情報収集の方法と活用の仕方を学んだことは、今後サイバーセキュリティの世界で活動していく上で、大きな助けになると思います。 NTTコミュニケーションズの皆さん、神田さん、本当にありがとうございました。 参考文献 C2: When Attackers Use Our Weapons Against Us Hunting Cobalt Strike Servers *1 : NA4Secプロジェクトについては 別のブログ記事 でも紹介しています
アバター
はじめに こんにちは、NTTコミュニケーションズの現場受け入れ型インターンシップに参加した奥です。 現在は大学でLinuxマルウェアの動的解析について研究しています。 今回私は2023年2月6日から2月17日の2週間、イノベーションセンターのNetwork Analytics for Security(通称NA4Sec)プロジェクトに参加し、 Cobalt Strikeを悪用した攻撃事例の調査、またCobalt Strikeのペイロード配布に利用される攻撃インフラの発見・追跡を体験しました。 この記事では、インターンシップ参加の経緯やインターンシップ期間中に取り組んだ内容について紹介します。 インターンシップ参加の経緯 私がこのインターンシップに応募したのは、夏に他社のインターンシップに参加した際に、他の学生たちがNTTのインターンシップを推していた記憶があり、応募しておけばよかった..と悔やんでいたためです。 後に調べてみると、冬季インターンシップも2週間という期間にわたって現場での業務体験ができ、セキュリティ分野だけでも様々な内容のポストがあることを知り、充実した経験ができると確信を持って応募しました。 しかし、実際に選考を受けてみると「私はただものではない」エピソードがないことに悩まされたり、インフラ関係のポストに応募したにもかかわらず、ネットワークの知識が全然ないことに不安を覚えたりしました。 そんな時、一次面接でNA4Secプロジェクトリーダーの神田さんとお話しし、とても穏やかな雰囲気の中、プロジェクトやインターンシップで取り組む内容について詳しく聞けました。一次面接は人事の方が担当することが多いイメージでしたが、初めに現場の方とお話しし、インターンシップの具体的なイメージができたことは、選考を受ける中で大きなモチベーションとなりました。 NA4Secプロジェクトとは 私が参加したNA4Secプロジェクトとは、「NTTはインターネットを安心・安全にする社会的責務がある」を理念として、攻撃インフラの解明、撲滅を目指すプロジェクトです *1 。 Tier1 ISP(Internet Service Provider)であり、MSSP(Managed Security Service Provider)でもあるNTTの強みを活かし、攻撃インフラを追跡するとともに、解析のための新たな技術を開発しています。 インターンシップで取り組んだこと 2週間で体験したインターンシップの内容をまとめます。 1. Cobalt Strikeの理解 Cobalt Strikeについての知識がなかったため、そもそもCobalt Strikeがどういったもので、なぜ攻撃に使われるのかを理解することからスタートしました。 「 Cobalt Strike 」はサイバー攻撃を再現し、システムや組織のセキュリティを評価するために使用される商用のセキュリティツールです。 一方で、Cobalt Strikeはクラック版(不正利用のために改変されたもの)が出回ったことにより、悪意のある攻撃にも広く利用されています。 主な機能として、システム侵入後に対象システムに Cobalt Strike Beacon (以下、Beacon)と呼ばれるバックドアプログラムを埋め込み、稼働させることでBeaconを介してPowerShellスクリプトの実行や、スクリーンショットの記録、ファイルのダウンロードなどの強力な攻撃モジュールを提供します。また、ネットワーク設定を調整することで通信を秘匿したり、初期アクセスのためのフィッシングメールを作成する機能も備えています。 攻撃者から見た時のCobalt Strikeの魅力として、攻撃技術の洗練度や人的・金銭的リソースの豊富さに関わらず、迅速に攻撃を行えることが挙げられます。また、広く利用されているために、正規のペネトレーションテストとの判別や、攻撃グループの特定を困難にする効果を狙っているケースもあるかもしれません。 2. Cobalt Strikeを悪用した攻撃事例の調査 次に、Cobalt Strikeを悪用した過去の攻撃事例を整理し、 脅威インテリジェンス としてまとめました。 具体的には攻撃グループや攻撃の手口、どういった行動を誰に対して行っているのかといった観点で情報を整理し、そのパターンから活動の背景や動機などを考察しました。 これらの情報を活用することにより、攻撃側が有利なサイバーセキュリティにおいて、防御側は攻撃被害が発生する前により効果的と考えられる対抗手段を取って攻撃に備えることができます。 今回は、攻撃事例の情報を整理するために、 ダイヤモンドモデル というサイバー攻撃のパターン分析手法を用いました。ダイヤモンドモデルは、以下の4つの特徴でサイバー攻撃を定義づけます。 攻撃グループ(Adversary) 攻撃者に関する情報(呼称,アカウント,関連するグループなど) 被害者(Victim) 被害者に関する情報(組織名,システム名,IPアドレスなどのネットワーク資産,デバイス,アカウントなど) 攻撃基盤(Infrastructure) 攻撃に際して使われた基盤(サービスアカウント,IPアドレス,ドメイン名,メールアドレスなど) 能力(Capability) 具体的な攻撃手法やツール(マルウェアやその送信方法,悪用された脆弱性など) 実際に、Cobalt Strikeに関する直近25件の攻撃事例をダイヤモンドモデルに当てはめてみました。情報源としては、 Microsoft Defender Threat Intelligence が無償で得られる情報の範囲でも多くの攻撃事例に対するレポートをまとめてくれており、効率的な情報の収集に役立ちました。 図1 ダイヤモンドモデルに基づき作成した表の一部 調査の結果、Cobalt Strikeは国家のスパイや犯罪グループにも利用されており、大規模な攻撃に使われていることが分かりました。 また、そのような国家を背景とした犯罪グループは、政府機関や重要インフラを狙うことが多く、標的とされる国にも特徴が見られました。 侵入経路としては、フィッシングメールや侵害したWebサイト、または脆弱性を利用することで最初のペイロードをダウンロードさせ、それを使うことでCobalt Strike Beaconを標的システムに配布していました。その後は、Cobalt Strikeの機能を使うことで、権限を昇格したり、特徴的なマルウェアをダウンロードする場合が多く見られました。 3. Cobalt Strike C2サーバの探索 Cobalt Strikeを悪用する攻撃者はインターネット上にTeam Serverと呼ばれるサーバを公開し、Beaconからの通信を待ち受けています。 このようなサーバを一般的にC2(Command & Control)サーバと言います。 インターンシップ後半では、主にインターネットスキャナ系検索エンジンを用いてCobalt Strike C2サーバの発見に挑戦しました。 インターネットスキャナ系検索エンジン 世の中にはインターネット上を定期的にスキャンして得られたデータを検索可能にしている検索エンジンサービスがあります。 例えば Censys は、毎日40億以上のIPv4ホストやWebサイトを定期的にスキャンしており、インターネット上に接続されている機器の情報(OSやソフトウェアのバージョン、IPアドレス、AS、ドメイン名、SSL/TLSサーバ証明書に関する情報)等で検索できます。 今回はCensysの他に Shodan 、 ZoomEye を加えた3つの検索エンジンを用い、それらを比較しました。 検索クエリの作成 膨大なデータベースの中からCobalt Strike C2サーバを見つけるためには、その特徴に合わせて効果的に条件を指定する必要があります。 ここでは、 Censys を用いて検索する方法を紹介します。 SSL/TLSサーバ証明書 最も誤検知を抑えてCobalt Strike C2サーバを探す方法の1つは、SSL/TLSサーバ証明書がCobalt Strikeでよく使われる証明書と一致しているものに絞ることです。 攻撃者がそれらのSSL/TLSサーバ証明書を使用している場合は、この方法で発見できます。 services.certificate: { "64257fc0fac31c01a5ccd816c73ea86e639260da1604d04db869bb603c2886e6", "87f2085c32b6a2cc709b365f55873e207a9caa10bffecf2fd16d3cf9d94d390c" } OR services.tls.certificates.leaf_data.issuer.common_name: "Major Cobalt Strike" OR services.tls.certificates.leaf_data.subject.common_name: "Major Cobalt Strike" 1~3行目 SSL/TLSサーバ証明書のFinger Print 4~5行目 SSL/TLSサーバ証明書の発行元(issuer)と主体者(subject) 実際にこの条件でフィルターをかけたところ、583件のCobalt Strike C2サーバと見られるIPv4ホストを発見できました(3月15日時点)。 図2 SSL/TLSサーバ証明書で絞った検索結果 ポート番号とHTTPヘッダ SSL/TLSサーバ証明書に基づく探索は、確実である反面、カスタマイズされた証明書が使われている場合や、HTTPなどのSSL/TLSで暗号化を行わない通信プロトコルの場合に発見できなくなるという弱点があります。 Cobalt Strike C2サーバのポート番号やHTTPヘッダに見られる特徴を組み合わせることで、そのような場合であってもCobalt Strike C2サーバである可能性の高いIPv4ホストを見つけられます。 services.port: 50050 AND services.http.response.status_code: 404 AND services.http.response.headers.content_type: text/plain AND services.http.response.headers.content_length: 0 AND NOT services.http.response.headers.server 1行目 使用ポートの条件はCobalt Strikeのデフォルトポートである50050ポート 2~5行目 HTTPヘッダの内容 実際に、インターンシップ用に立てられたCobalt Strike C2サーバに対してスキャンしてみたところ、図3の赤枠内のHTTPヘッダを持つレスポンスが得られました。 これは一般的なCobalt Strike C2サーバのHTTPヘッダの特徴と一致しています。 図3 Cobalt Strike C2サーバからのレスポンス 実際にこの条件でフィルターをかけたところ、294件のサーバにヒットしました(3月15日時点)。 それぞれのIPv4ホストについて詳しく見てみると、多くがCobalt Strikeの証明書や自己署名証明書を利用しているなど、攻撃インフラの可能性が十分に考えられるものでした。 図2 ポート番号とHTTPヘッダで絞った検索結果 検体の解析によるC2の発見 Censysにはない機能として、ShodanやZoomEyeはスキャン対象となるサーバからBeaconを入手し、解析することによってCobalt Strike C2サーバかどうかを判定しています。また、以下のように解析結果を得ることも可能です。 Cobalt Strike Beaconの解析結果(Shodan) インターネットスキャナ系検索エンジンの比較 今回用いた3種類のインターネットスキャナ系検索エンジンについて、Cobalt Strike C2サーバを探索する際の長所と短所をまとめます。 情報源 pros cons Censys スキャンしているポートの数が多い(50050ポートの結果を得られる),スキャン頻度が高い Beaconの入手と解析はしていない Shodan Beaconの解析結果が得られる、スキャン頻度が比較的高い スキャンしているポートの数が少ない ZoomEye Beaconの解析結果が得られる スキャン頻度が低い、中国サーバのスキャン結果が表示されない インターンシップを振り返って サイバーセキュリティの最前線で長年戦ってきた方に、2週間の間マンツーマンで指導していただけたことは、本当に贅沢な経験だったと思います。 ネットワークの基礎的な構成技術から、研究で注目している新たな技術まで、幅広く深く触れることのできた密度の濃い2週間でした。 脅威を見定め、能動的に情報を収集して防御に活用する アクティブサイバーディフェンス という、今まで私の中になかった視点を得ることができたこと、 そのための情報収集の方法と活用の仕方を学んだことは、今後サイバーセキュリティの世界で活動していく上で、大きな助けになると思います。 NTTコミュニケーションズの皆さん、神田さん、本当にありがとうございました。 参考文献 C2: When Attackers Use Our Weapons Against Us Hunting Cobalt Strike Servers *1 : NA4Secプロジェクトについては 別のブログ記事 でも紹介しています
アバター
イノベーションセンターの三島です。 本記事では、RFC や Internet-Draft に準拠したコントローラーやプロトコルライブラリの開発について、 NTT Com が公開中の Segment Routing (SR) 用のコントローラー、 Pola PCE の開発経験を基にご紹介します。 商用機器と相互接続可能なコントローラー・プロトコルライブラリを開発してみたい方、SR をはじめとするネットワークを運用中で、機能拡張が可能なコントローラーを導入してみたい方は是非ご覧ください! 以降では、コントローラー開発手順の概要を Pola PCE の実装例を基に解説した後、プロトコルライブラリの作り方と機能追加の方法、相互接続試験と OSS へのマージまでの流れを解説します。 例として扱う Pola PCE 自体の詳細や活用例については解説しないため、詳細を知りたい方は下記の資料をご参照ください。 Pola PCE の開発経緯と OSS 化 Segment Routing 用 Stateful PCE を フルスクラッチで開発した話 NTT Com で OSS を作って公開してみた - やったことリスト共有 PCE・コントローラーの利用例 大規模 SR 網の運用を効率化するネットワークコントローラーの開発 Pola PCE で SR 網の TE を体験してみよう! コントローラー・プロトコルライブラリを作ってみよう! コントローラーはソフトウェアによりネットワークを集中管理する役割を持ち、主に運用やサービス提供の効率化などを目的として用いられます。 特に、大規模商用網のように複数のルーターが存在する環境においては、機器の一元管理による運用コストの低減や、スケーラビリティの高い運用が期待されます。 また、このようなネットワークは複数のベンダーの機器を用いて構成されることもあります。 これらの理由から、本記事では下記のポイントを満たすコントローラーを開発します。 複数のクライアントを収容可能なサーバー セッションごとに状態を管理し、並行処理可能な実装 各ベンダーの商用機器と相互接続可能な、RFC 準拠なプロトコルライブラリ プロトコルの構造や基本的なパケット操作の実装 以降の節では、要件を満たすようなサーバー機能の開発手順と、RFC 準拠なプロトコル実装について解説します。 コントローラーとプロトコルライブラリの開発方法をご紹介するにあたり、SR のコントローラーである Path Computation Element (PCE) を例として用います。 PCE は、SR を初めとする Traffic Engineering (TE) 技術で構成されたネットワークにおいて、TE を管理するためのコントローラーであり、TE により網全体の性能を考慮した QoS の向上や SFC 提供などを実現する役割を持ちます。 サーバー開発の概要 まずは、コントローラーのサーバー機能を開発する上で必要となるソケットプログラミングや並行処理、セッション管理の手法についての概要をご紹介します。 ソケットプログラミングによる TCP サーバー実装 PCEP は TCP ベースのプロトコルであり、TCP ソケットプログラミングが必要となります。 一般的なソケットプログラミングの概要を示します。 図の通り、一般的なソケットプログラミングにおけるサーバー実装では、ソケットを作成する socket 、機器のアドレス・ポートとソケットを紐付ける bind 、当該ポートで TCP の待ち受けを行う listen 、クライアントからの接続を受け入れてセッションを構築する accept という流れで TCP セッションを構成します。 Pola PCE では Go の net パッケージを利用し、 pkg/server/server.go の Serve メソッドとして実装を行なっています。 net.ListenTCP が socket・bind・listen を行い、 ListenTCP の返り値である *TCPListener の AcceptTCP() メソッドにより accept を行います。 並行処理とセッション管理 PCE のようなコントローラーでは、複数のクライアントを管理することが求められます。 そのため、サーバー機能として 並列処理 と セッション管理 の機能が必要となります。 図に Pola PCE の並行処理とセッション管理の実装例を示します。 Pola PCE では、セッションは pkg/server/session.go に実装した Session 構造体を用いて管理を行なっています。 Session 構造体では、セッション管理に必要となるクライアントのアドレスやソケットの情報に加え、SRP-ID や Stateful PCE として管理するクライアントの SR Policy、Keepalive の間隔など、クライアントごとに固有のパラメータを全て管理させています。 並列処理は goroutine により実現しています。 TCP サーバーが AcceptTCP() を実行した後、そのセッションを管理するための Session 構造体を作成します。 その後 goroutine を作成して Session 構造体の Established() メソッドを呼び出し、PCE としてのメッセージ送受信や SR Policy の発行など、そのクライアントに対する PCE 機能を提供します。 この仕組みにより、複数のクライアントを区別しつつ複数収容するコントローラーを構成することが可能となります。 プロトコルライブラリ開発 RFC 準拠のプロトコルライブラリの開発について、Pola PCE への Close メッセージの追加 を例に解説します。 RFC の読み解きによるプロトコルの確認 Open/Close/Keepalive などのPCEP メッセージは RFC5440 で提案されています。 4.2.7 節に記載されている通り、Close メッセージは PCEP の通信において TCP セッションを終了させるために用いられます。 本記事ではプロトコルライブラリに Close を実装した後、Pola PCE から特定のピアに Close メッセージ送り、PCEP のセッションを切断する実装を追加します。 以降の説明のため、PCEP の基本構造を図に示します。 図の通り、PCEP は単一の common header と複数の object から構成されます。 common header にはそれが何の PCEP メッセージであるかを示す message type が格納されています。 RFC5440 の 6.1 節の通り、主なメッセージは 7 種類存在し、今回実装する Close の message type は 7 と定義されています。 各 object は common object header と object body から構成されています。 common object header には、その object を示す object class と object type が格納されています。 RFC5440 の 7.17 節に、object class は 15、object type は 1 と定義されています。 RFC5440 の 6.8 節には、Close メッセージは 1 つの CLOSE object を含むと書かれています。 message type・object class・object type など、プロトコルで決められたリソースは、IANA が管理しています。IANA は管理するリソースを https://www.iana.org にまとめて掲載しているため、プロトコル開発等の際はこちらを参照すると良いです。 PCEP の場合は Path Computation Element Protocol (PCEP) Numbers を参照してください。 プロトコルライブラリの実装 - Pola PCE への Close メッセージの追加 ここからは実際に Pola PCE へ Close メッセージを追加します。 message type は pkg/packet/pcep/message.go に、object class は pkg/packet/pcep/object.go に実装済みのため今回は追加不要です。 const ( MT_CLOSE uint8 = 0x07 // RFC5440 ) const ( OC_CLOSE uint8 = 0x0f // RFC5440 ) Close メッセージの構造体とパケット操作に関するメソッドを追加します。 Pola PCE では、広く用いられる BGP ライブラリである GoBGP と同様のパケット操作メソッドを採用しています。 パケット操作メソッドとしては、バイト列を構造体に格納する DecodeFromBytes() 、構造体をバイト列に変換する Serialize() の 2 つのメソッドと、新たに構造体を作成する NewCloseMessage 関数が必要となります。 // Close Message type CloseMessage struct { CloseObject *CloseObject } func (m *CloseMessage) DecodeFromBytes(messageBody [] uint8 ) error { var commonObjectHeader CommonObjectHeader if err := commonObjectHeader.DecodeFromBytes(messageBody); err != nil { return err } closeObject := &CloseObject{} if err := closeObject.DecodeFromBytes(messageBody[COMMON_OBJECT_HEADER_LENGTH:commonObjectHeader.ObjectLength]); err != nil { return err } m.CloseObject = closeObject return nil } func (m *CloseMessage) Serialize() [] uint8 { closeMessageLength := COMMON_HEADER_LENGTH + m.CloseObject.getByteLength() closeHeader := NewCommonHeader(MT_CLOSE, closeMessageLength) byteCloseHeader := closeHeader.Serialize() byteCloseObject := m.CloseObject.Serialize() byteCloseMessage := AppendByteSlices(byteCloseHeader, byteCloseObject) return byteCloseMessage } func NewCloseMessage(reason uint8 ) (*CloseMessage, error ) { o, err := NewCloseObject(reason) if err != nil { return nil , err } m := &CloseMessage{ CloseObject: o, } return m, nil } 同様に、close object の object type と構造体とメソッド群も実装します。 こちらも message と同じメソッドに加え、Object 長の計測のため、 Len() メソッドを実装します。 // Close Object (RFC5440 7.17) const ( OT_CLOSE_CLOSE uint8 = 0x01 ) const ( R_NO_EXPLANATION_PROVIDED uint8 = 0x01 R_DEADTIMER_EXPIRED uint8 = 0x02 R_RECEPTION_OF_A_MALFORMED_PCEP_MESSAGE uint8 = 0x03 ) type CloseObject struct { Reason uint8 } func (o *CloseObject) DecodeFromBytes(objectBody [] uint8 ) error { o.Reason = objectBody[ 3 ] return nil } func (o *CloseObject) Serialize() [] uint8 { closeObjectHeader := NewCommonObjectHeader(OC_CLOSE, OT_CLOSE_CLOSE, o.getByteLength()) byteCloseObjectHeader := closeObjectHeader.Serialize() buf := make ([] uint8 , 4 ) buf[ 3 ] = o.Reason byteCloseObject := AppendByteSlices(byteCloseObjectHeader, buf) return byteCloseObject } func (o *CloseObject) Len() uint16 { // CommonObjectHeader(4byte) + CloseObjectBody(4byte) return COMMON_OBJECT_HEADER_LENGTH + 4 } func NewCloseObject(reason uint8 ) (*CloseObject, error ) { o := &CloseObject{ Reason: reason, } return o, nil } close object は RFC5440 の 7.17 節で下記のように定義されています。 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Reserved | Flags | Reason | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | // Optional TLVs // | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ また、7.17節では Reserved 領域と Flag 領域は 0 でパディングし、read 時には無視と規定されています。 そのため、close object の構造体である type CloseObject には、Reason 領域だけを uint8 で用意しておき、 DecodeFromBytes() 、 Serialize() は Reason のみを扱うように実装します。 次に、Close メッセージの受信処理を追加します。 pkg/server/session.go の func (ss *Session) ReceivePcepMessage() に、message type が Close であるメッセージを受信した場合の処理を追加します。 case pcep.MT_CLOSE: byteCloseMessageBody := make ([] uint8 , commonHeader.MessageLength-pcep.COMMON_HEADER_LENGTH) if _, err := ss.tcpConn.Read(byteCloseMessageBody); err != nil { return err } closeMessage := &pcep.CloseMessage{} if err := closeMessage.DecodeFromBytes(byteCloseMessageBody); err != nil { return err } ss.logger.Info( "Received Close" , zap.String( "session" , ss.peerAddr.String()), zap.Uint8( "reason" , closeMessage.CloseObject.Reason), zap.String( "detail" , "See https://www.iana.org/assignments/pcep/pcep.xhtml#close-object-reason-field" )) // Close session if get Close Message return nil まず byteCloseMessageBody という byte 列を作成し、 ss.tcpConn.Read() により read した object を格納します。その後 DecodeFromBytes() メソッドにより、 closeMessage 構造体に受信した close message を格納しています。 Close を正しく受信・デコードした後、ログに close を受信した旨と受信した close の Reason を記録した後 ReceivePcepMessage() から return することで、 Established() に戻りセッションの close 処理を行います。 以上で、RFC に準拠したパケットフォーマットの定義と TCP セッションからの read/write 処理、close の処理が全て実装できました。 サーバー機能の実装 - Pola PCE への Close メッセージ生成コマンドの追加 運用・検証時に任意のタイミングで特定のピアとの Close を行うため、 gRPC API の追加 と、 コマンドを介した Close メッセージの送信機能を実装 します。 func (ss *Session) SendClose(reason uint8) error { closeMessage, err := pcep.NewCloseMessage(reason) if err != nil { return err } byteCloseMessage := closeMessage.Serialize() ss.logger.Info("Send Close", zap.String("session", ss.peerAddr.String()), zap.Uint8("reason", closeMessage.CloseObject.Reason), zap.String("detail", "See https://www.iana.org/assignments/pcep/pcep.xhtml#close-object-reason-field")) if _, err := ss.tcpConn.Write(byteCloseMessage); err != nil { return err } return nil } 今回必要となるのはあるセッションに対するメッセージの送信処理であるため、 pkg/server/session.go 内に、 Session 構造体の SendClose() メソッドとして実装します。 SendClose では、 NewCloseMessage() 関数で close message を作成し、 Serialize() メソッドにより byte 列に変換、 ss.tcpConn.Write() によりソケットへと write します。 Pola PCE はマイクロサービスとしての活用を前提とし、gRPC API を有しています。また、標準コマンドとして、デーモンである polad に対する gRPC client となる pola コマンドを提供しています。 ここでは、 pola に pola session del <Address> オプションを実装し、指定したピアへ SendClose を送信可能とします。 まず、 api/grpc/pola.proto に DeleteSession の RPC を作成します。 rpc DeleteSession (Session) returns (RequestStatus) {}; 次に、polad 側の処理として pkg/server/grpc_server.go に DeleteSession メソッドを作成します。 func (c *APIServer) DeleteSession(ctx context.Context, input *pb.Session) (*pb.RequestStatus, error) { ssAddr, _ := netip.AddrFromSlice(input.GetAddr()) s := c.pce ss := s.SearchSession(ssAddr) if err := ss.SendClose(pcep.R_NO_EXPLANATION_PROVIDED); err != nil { return &pb.RequestStatus{IsSuccess: false}, err } // Remove session info from PCE server s.closeSession(ss) return &pb.RequestStatus{IsSuccess: true}, nil } このメソッドは、gRPC により DeleteSession が実行された際、当該セッションに対して SendClose 処理を実行し、成功/失敗のステータスを送信します。 次に、pola コマンドが用いる gRPC client 側の関数を cmd/pola/grpc_client.go に実装します。 func deleteSession(client pb.PceServiceClient, session *pb.Session) error { ctx, cancel := withTimeout() defer cancel() _, err := client.DeleteSession(ctx, session) if err != nil { return err } return nil } deleteSession() 関数が実行されると、gRPC client として DeleteSession メソッドが実行されます。 最後に、pola コマンドに session del オプションを追加します。 pola コマンドは Cobra を用いて実装しています。 まず、pola session コマンドに del オプションを追加します。 cmd/pola/session.go の newSessionCmd() 関数に newSessionDelCmd() の呼び出しを追加します。 func newSessionCmd() *cobra.Command { cmd := &cobra.Command{ Use: "session" , RunE: func (cmd *cobra.Command, args [] string ) error { if err := showSession(jsonFmt); err != nil { return err } return nil }, } cmd.AddCommand(newSessionDelCmd()) return cmd } 次に cmd/pola/session_del.go に del コマンドそのものである newSessionDelCmd() を実装します。 package main import ( "fmt" "net/netip" pb "github.com/nttcom/pola/api/grpc" "github.com/spf13/cobra" ) func newSessionDelCmd() *cobra.Command { return &cobra.Command{ Use: "del" , SilenceUsage: true , RunE: func (cmd *cobra.Command, args [] string ) error { if len (args) < 1 { return fmt.Errorf( "requires session address \n Usage: pola session del [session address]" ) } ssAddr, err := netip.ParseAddr(args[ 0 ]) if err != nil { return fmt.Errorf( "invalid input \n Usage: pola session del [session address]" ) } if err := delSession(ssAddr, jsonFmt); err != nil { return err } return nil }, } } func delSession(session netip.Addr, jsonFlag bool ) error { ss := &pb.Session{ Addr: session.AsSlice(), } err := deleteSession(client, ss) if err != nil { return err } if jsonFlag { fmt.Printf( "{ \" status \" : \" success \" } \n " ) } else { fmt.Printf( "success! \n " ) } return nil } newSessionDelCmd は、 pola session に del オプションが指定された時に、 func delSession() を実行します。 func delSession() は cmd/pola/grpc_client.go の deleteSession() を実行することで、gRPC client としてセッション削除の gRPC API を実行します。 これにより、 pola session del <Address> オプションによる Close 機能を追加できました。 動作試験 機能を追加した Pola PCE とベンダー機器との相互接続試験を行います。 今回は WIDE Project の高田さん( @Enigamict )に作成していただいた example/containerlab/sr-mpls_pcep を使用し、下記のトポロジーを用いて検証します。 ここでは、PCEP の検証用に Cisco/Juniper/FRRouting の PE が 1 台ずつ存在する環境を作成しています。 このトポロジーをはじめ、SR-MPLS や SRv6 で TE の検証が可能な環境を example として公開しています。 機能を追加した Pola PCE を Docker コンテナとしてビルドすることで、これらの環境を使って検証できます。 Linux + Docker 環境があれば試せるので、是非手元で動かしてみてください。 Docker を用いたネットワークエミュレータツールである Containerlab のインストールや、各イメージの準備方法、ネットワークの起動などの流れは REAME.md にまとめています。 起動したネットワーク上で Pola PCE のコンテナに入り、 polad コマンドで Pola PCE の起動&セッションの構築後、 pola session コマンドで確認します。 # polad -f polad.yaml > /dev/null 2>&1 & # pola session sessionAddr(0): 10.0.255.1 sessionAddr(1): 10.0.255.3 sessionAddr(2): 10.0.255.2 次に、今回追加した pola session del コマンドを実行し、IOS XR/Junos/FRRouting Close メッセージを送信します。 以下では IOS XR に対する Close を実施します。 root@pola-pce:/# pola session del 10.0.255.1 success! また、動作試験のため Wireshark を利用して PCEP パケットをキャプチャします。下記に、SSH 経由で Containerlab 環境からパケットキャプチャを実施する例を示します。 ssh $clab_host "sudo -S ip netns exec clab-srv6_te_l3vpn-pe01 tcpdump -U -nni eth1 -w -" | wireshark -k -i - キャプチャした結果、実装通りに Close メッセージが送られ、TCP も Close していることが確認できます。 これにより、実装した Close 機能が各ルーターと正しく相互接続可能なことを確かめられました。 Let's contribute! 最後に、今までに実装した機能を Pola PCE へ Pull Request として提出しましょう。 テンプレートに従って GitHub PR を作成します。 今回の例にあげた Close 機能は PR #66 で取り込まれています。 この PR では、同時に PCErr 機能も追加されています。もし興味があれば、是非そちらの読み解きにもチャレンジしてみてください。 また、今回の例にあげた Close 機能は Pola PCE v1.2.1 でリリース済みのため、 最新の Docker イメージ や、それを用いた example でも試せます! まとめと今後の予定 本記事では、Pola PCE の実装例を基に、RFC/I-D 準拠のコントローラーやプロトコルライブラリ開発の進め方についてご紹介しました。 記事を通じてプロトコルライブラリやコントローラーの実装に興味を持たれた方は、是非我々と共に Pola PCE を開発しましょう! 次のステップとして、 uSID や Flex-Algo を用いた Dynamic TE の実装を目指しています。実装予定の機能は issue や milestone として整備していますので是非ご確認ください。 本記事を読んでコントローラーやプロトコルライブラリの開発に興味をもった方や、拡張可能な PCE を求めている方がいましたら、Pola PCE にコントリビューションしてみませんか? もしご興味をお持ちの方がいれば、是非気軽にご連絡や PR/Issue の作成をお願いします!
アバター
はじめに こんにちは、インターン生の 山口雄翔 です。 この度2023年2月6日から2週間、NTTコミュニケーションズのインターンシップに、 エンタープライズ向け大規模クラウドサービスを支えるネットワーク開発 というテーマで参加させていただきました。 この記事ではその体験について書かせていただきます。 インターンシップについて NTTコミュニケーションズでは、 SDPFクラウド/サーバー というエンタープライズ向けクラウドサービスを提供しています。 このサービスでは、仮想的なL2ネットワークを自由に構築し、そこにベアメタルサーバ・VM・ストレージなどを(論理的に)接続させて使うことができます。 もちろんインターネット接続やロードバランサなどをサービスとして利用することもできます。 そのクラウドの内部のネットワークを制御するSDNコントローラの改善というのが、本インターンシップのテーマでした。 前半1週間はこのクラウド内のネットワークやSDNとその関連技術について教えてもらいながら学び、後半1週間でコントローラの性能改善に取り組みました。 クラウド内のネットワークとSDNについて 概要 クラウド内のネットワーク構成はこのようになっています。(紫で印をつけた部分がSDNコントローラ) クラウドを構成するデータセンタのスイッチは全て隣接するスイッチとBGPで接続していて、全体に対して疎通性があります。 機能を持つノード(ハイパーバイザ・ベアメタルサーバ・ゲートウェイ等)はLeafスイッチの下にあります。 VMを収容するハイパーバイザが乗っているマシン上では、VMに関係する経路情報をやり取りするための仮想ルータが動いています。 EVPN/VXLAN VMやベアメタルサーバ同士のL2接続性を確保するために、VXLANとEVPNを利用しています。 EVPN(Ethernet VPN)とは、仮想的にL2接続性を提供するためのVPNのことです。 仮想L2ネットワークに接続するノードのMACアドレス VPNのエンドポイントのIPアドレス 仮想L2ネットワークの識別子 などをMP-BGPに乗せて交換することで、L2接続に必要な情報を共有し、ネットワークをスケールさせることができます。 https://datatracker.ietf.org/doc/html/rfc8365 https://datatracker.ietf.org/doc/html/rfc7432 VXLANはパケットをカプセル化することで、L3ネットワーク上に論理的なL2ネットワークを構築するトンネリングプロトコルです。 MACアドレスなどの情報をEVPNで交換したのち、VXLANでL2トンネルを作ることによって、L2接続性を実現しています。 図のようにencap/decapを行うことで、仮想L2ネットワークを実現しています。 (encap/decapをするVXLANトンネルのエンドポイントのことを、VTEPと呼びます。) このように仮想ネットワークが実現できるような経路制御の管理をするのも、SDNコントローラの重要な役割のひとつです。 (次項参照) SDNコントローラ SDPFクラウド/サーバーでは、SDNコントローラに Tungsten Fabric を採用しています。 SDNコントローラは、 仮想L2ネットワーク等のリソース作成のためのインターフェースの提供およびVTEPへの設定投入 経路情報の計算と広告 コントローラ自身やリソース情報のメトリクスの収集 を持っています。 (3についてはインターンシップで扱っていないため、この記事では触れません。) 1について クラウド内の仮想計算機基盤はOpenStackを利用して作られています。 OpenStackのネットワーク周りを管理するNWコントローラとTungsten Fabricを連携させることにより、仮想基盤側から投入された設定が、SDNコントローラを経由してネットワークに入るような設計を実現しています。 2について SDNコントローラは、クラウドのネットワーク内で仮想L2ネットワークを作るための経路情報の計算・広告をする役割を担っています。 以下の図は、コントローラとネットワークの各コンポーネントの論理的な繋がりを示しています。 コントローラと仮想ルーターの経路情報のやり取りでは、XMPPというプロトコルに乗せてEVPNの経路情報のやり取りをしています。 物理ルータがVTEPとなっている箇所については、Route Reflector(以下RRと示す)がコントローラと物理ルーターの双方とBGPピアを張ってEVPNの経路情報を交換することで、コントローラからの経路情報をRR経由でVTEPに伝えることができます。 対外発表資料 SDPFクラウド/サーバーについての技術的な対外発表の資料を以下にまとめましたので、ご興味のある方はぜひご覧ください。 JANOG: EVPN Anycast Gateway を 商⽤導⼊した話 : https://www.janog.gr.jp/meeting/janog48/evpn/ JANOG: エンタープライズ向けクラウドのSDN基盤の安定化への挑戦 : https://www.janog.gr.jp/meeting/janog44/program/sdnclg/ NTT Com Open TechLunch: クラウドの作り方 : https://speakerdeck.com/toby06/how-to-create-cloud-service 今回取り組んだ問題について コントローラ内での経路の保持と複製処理 SDNコントローラはクラウドネットワークの経路情報を全て把握しています。 コントローラの内部では、各仮想ネットワークごとのルーティングテーブル(VRF)とFabric全体の経路を保持するグローバルルーティングテーブルを持っており、EVPNについてもこの構成で経路を保持しています。 そしてVRFテーブルとグローバルルーティングテーブルの間での経路の複製処理がイベントドリブンで実行されるようになっているのですが、コントローラのこの動作が時間のかかる処理となっています。 (Tungsten Fabric は複数コアを用いたマルチスレッドに対応していますが、経路複製がスレッドセーフでない処理を含むため、1コアのみを用いて順番に処理を行うタスクキューに入るようになっており、時間がかかってしまいます。) ライブマイグレーションなどに伴う経路計算の際に、この部分の計算時間がボトルネックとなって経路計算の完了が遅れ、それによって通信ができない時間が発生してしまいます。 それを防ぐためには経路複製処理の速度の改善が必要であり、今回のインターンシップではその問題に取り組みました。 作業 実は経路複製処理のコード改善というのはインターンシップの前からチームで取り組まれていて、upstreamにmergeされている変更もあります。 (その説明は こちらの資料 に書いてあります。) その中で同じ関数内に更なる改良の余地がありそうだとが分かり、自分はそれに取り組ませていただきました。 まずは教えてもらった関数内の処理内容を把握した上で、部分ごとに分けて時間を測りました。 この関数の中には、 経路を複製すべき宛先テーブルを種々の条件から絞る処理 宛先テーブルをRouteTarget等から決定する処理 実際に経路情報をメモリコピーする処理 などが含まれており、それらの処理ごとの時間をログに出力したのちpythonスクリプトを使って結果を見やすく表示しました。 (下はその様子です。) そして、 計測結果・プログラムのコード・そこで扱われる経路情報 の3つを見ながら考えて、処理時間を短縮できそうな箇所を見つけました。 コード改善 アルゴリズム コントローラが保持している経路(BGP route)には、複数のpathを持つものがあります。 例えば3つのRRから同じ経路を受け取った際、src addrだけ異なる3つのpathがrouteに登録されます。 ある1つのrouteの複製は、含まれるpathごとにループで行われる実装となっていて、その中でpathの複製先EVPNテーブルを選定していました。 (pathの中の拡張コミュニティに含まれるRouteTarget等の情報からテーブルを探索し、複製先テーブルリストを作っています。) ですが同じrouteに属するpathは(自分が検証環境でチェックした限りでは)複製先テーブルは同じであったため、ここの処理を1つにまとめることで、性能改善ができるのではないかと考えました。 この効果は、多量(ほぼ全て)のVRFテーブルに複製する必要がある EVPN Type1経路 において顕著に現れました。 (EVPN Type1経路はマルチホーミングの情報を広告するためのものです。) 実装結果・考察 改善前と後のパフォーマンス計測の結果 before after この写真は経路複製をする関数の中の部分ごとの処理時間を出力したものです。 印をつけた部分が複製先EVPNテーブルを選定して該当するテーブルをリストに挿入している操作なのですが、コード改善後に処理時間が大幅に短縮されていることが分かります。 この関数全体の処理時間(左端の数値)は1割程度の削減でき、性能として10%程度の改善を実現できたと言えます。 ( 数値の単位は全てマイクロ秒です。 ) 処理の測定、実装を通して想定通り性能改善を得ることはできました。 一方で、インターンシップの間ではその他に極端にボトルネックとなっているような処理を見つけることはできず、処理速度の大幅改善の難しさなども実感しました。 課題 時間がなくてできませんでしたが、複製先テーブル計算の処理をまとめても本当に大丈夫かというのを、ロジック・動作検証の両面から確認してゆく必要があるかなと思いました。 (商用環境へのデプロイまでには他にも様々な段階を踏んだ検証・テストが必要で、今回自分が書いたコードがそのまま乗ることはありません。) 感じたこと 大規模ネットワークを制御するSDNコントローラを作る難しさ プログラム中の本当に些細なロジックの誤りであっても、コントローラから経路情報が流れる中でその影響がクラウド内に広く波及して、全体として見た時に大きなエラーになってしまうことが多々あると思いました。(実際自分も検証環境を壊しかけました。) 大規模SDNコントローラだからこそコード1行1行にこだわって書かなければいけないなと思いましたし、そのような環境で日々コードを書かれているチームメンバーの皆さんの、コードを検証する時の鋭さを強く感じました。 また小規模な検証環境で動かした時には気にならなかった少しの遅延が、大規模環境に移した途端顕著になるということも経験し、コードを最適化する重要性を身をもって体験できました。 デバッグ SDNを用いる場合、コントロールプレーンでのエラーは基本SDNコントローラに原因があります。 原因となるコンポーネントの特定という意味では分かりやすい反面、ロジックの難しさやネットワークの状態管理の煩雑さが全てこのソフトウェアの内部に含まれるため、コードや挙動が非常に複雑です。 またそもそもエラーを再現すること自体が難しく、デバッグに高度な技術力や経験が必要であるなと感じました。 人間よりも機械の気持ちが分かる(他称)というある先輩は、エラーが起こった時迷いなくgdbでこの巨大でステートフルなプログラムの動作を見に行っていました。つよい... 謝辞 インターンシップに向けて検証環境整備や資料準備をしてくださり、期間中は丁寧に業務のことを教えてくださったチームの皆さんのおかげで、濃くて楽しい2週間になりました。本当に本当にありがとうございました。 本来は自分が参加した部署ではインターンブログを書くことはあまりないそうなのですが、とても面白くて素晴らしい体験ができたので、技術的なことを含めて是非共有したいと思い、無理を言って書かせていただきました。 (メールで質問攻めにしてしまいすみません。) 長い文章となってしまいましたが、多くの方にこのブログを読んでいただけると嬉しいです。
アバター
はじめに シェル芸勉強会について 問題と解説 Q1(@butackle66さんから) 解答例1 解答例2 Q2(@butackle66さんから) 解答例1 解答例2 Q3 解答例1 解答例2 Q4 解答例1 解答例2 Q5 解答例 Q6 解答例 LT LT1 curlでTelegram botを操作 (やべえ @yabeenico) LT2 音声合成してみよう (たいちょー @xztaityozx_001) おわりに はじめに こんにちは。デジタル改革推進部データドリブンマネジメント推進部門の江川尋喜 (Hiroki Egawa / @yabeenico ) です。 第63回ビッグハンドタウンシェル芸勉強会が2023年02月25日に開催されました。 今回は NTT Com のオフィスビル、大手町プレイスを会場提供させていただきました。 このブログエントリでは、勉強会で出題された問題の解答と解説をします。 問題データ ryuichiueda/ShellGeiData/vol.63 Twitter まとめ 第63回ビッグハンドタウンシェル芸勉強会 - Togetter YouTube 第63回シェル芸勉強会 - YouTube イベントサイト jus共催 第63回ビッグハンドタウンシェル芸勉強会 | Doorkeeper イベントリンク集 jus共催 第63回シェル芸勉強会リンク集 | 上田ブログ 大手町会場の様子 シェル芸勉強会について まずシェル芸について、提唱者の上田隆一 ( @ryuichiueda ) 先生がこう定義しています。 [シェル芸とは] シェル芸の定義バージョン1.1 マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。あるいはそのときのコマンド入力のこと。 要は Unix系OSのシェル上でのワンライナーのことです。勝手に名前つけてすんません。 https://b.ueda.tech/?page=01434 シェル芸勉強会はシェル芸を極めたい人、すなわちシェルスクリプトやコマンドの愛好家が参加し、交流します。 参加者のバックグラウンドはコマンド開発者、インフラエンジニア、研究者、学生等様々です。 シェル芸勉強会は次の流れで進行します。 上田先生が作ってきた問題を出題 参加者が Twitter に #シェル芸 を付けて解答を投稿 上田先生が解答を解説 #シェル芸 をつけてつぶやくとシェル芸bot ( @minyoruminyon ) が実行結果を表示してくれます。 また、入力に使うデータは ryuichiueda/ShellGeiData/vol.63 にあります。 今回紹介する解答は私が考えたものと、他の人の考えを取り入れたものがあります。 また、文字数がなるべく少なくなる解答を用意しました。 それでは問題、解答及び解説を見ていきましょう。 問題と解説 Q1( @butackle66 さんから) 1x1〜9x9の九九の答えをすべて足し合わせてください。 https://twitter.com/ryuichiueda/status/1629339996028018688?s=20 解答例1 文字数が少ないです。 $ echo {1..9}*{1..9}+ 0|bc 2025 解説 bash のブレース展開で計算式を出力し、bc で計算しています。 検索ワード: ブレース展開 / brace expantion / sequence expantion $ echo {1..9} 1 2 3 4 5 6 7 8 9 $ echo {1..9}{1..9} 11 12 13 14 15 16 17 18 19 21 22 23 24 25 26 27 28 29 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 47 48 49 51 52 53 54 55 56 57 58 59 61 62 63 64 65 66 67 68 69 71 72 73 74 75 76 77 78 79 81 82 83 84 85 86 87 88 89 91 92 93 94 95 96 97 98 99 $ echo {1..9}*{1..9}+ 0 1*1+ 1*2+ 1*3+ 1*4+ 1*5+ 1*6+ 1*7+ 1*8+ 1*9+ 2*1+ 2*2+ 2*3+ 2*4+ 2*5+ 2*6+ 2*7+ 2*8+ 2*9+ 3*1+ 3*2+ 3*3+ 3*4+ 3*5+ 3*6+ 3*7+ 3*8+ 3*9+ 4*1+ 4*2+ 4*3+ 4*4+ 4*5+ 4*6+ 4*7+ 4*8+ 4*9+ 5*1+ 5*2+ 5*3+ 5*4+ 5*5+ 5*6+ 5*7+ 5*8+ 5*9+ 6*1+ 6*2+ 6*3+ 6*4+ 6*5+ 6*6+ 6*7+ 6*8+ 6*9+ 7*1+ 7*2+ 7*3+ 7*4+ 7*5+ 7*6+ 7*7+ 7*8+ 7*9+ 8*1+ 8*2+ 8*3+ 8*4+ 8*5+ 8*6+ 8*7+ 8*8+ 8*9+ 9*1+ 9*2+ 9*3+ 9*4+ 9*5+ 9*6+ 9*7+ 9*8+ 9*9+ 0 $ echo 1 + 2 | bc 3 解答例2 ブレース展開と bc のどちらも利用しない別解です。 ブレース展開では変数が使えないので、変数が使いたシチュエーションで利用できます。 $ join -j9 <(seq 9) <(seq 9) | awk '$0=a+=$1*$2' | tail -n1 2025 解説 join -j9 <(seq 9) <(seq 9) -> 列結合を利用して 1 1 から 9 9 までの組を作っています。 ### サンプルデータ $ cat <(printf '1 a\n2 b\n3 c\n4 d\n') <(printf '1 e\n2 f\n3 g\n4 h') 1 a 2 b 3 c 4 d 1 e 2 f 3 g 4 h ### 1列目で結合 $ join -j1 <(printf '1 a\n2 b\n3 c\n4 d\n') <(printf '1 e\n2 f\n3 g\n4 h') 1 a e 2 b f 3 c g 4 d h ### 存在しない列で結合すると full join となる $ join -j9 <(printf '1 a\n2 b\n3 c\n4 d\n') <(printf '1 e\n2 f\n3 g\n4 h') 1 a 1 e 1 a 2 f 1 a 3 g 1 a 4 h 2 b 1 e 2 b 2 f 2 b 3 g 2 b 4 h 3 c 1 e 3 c 2 f 3 c 3 g 3 c 4 h 4 d 1 e 4 d 2 f 4 d 3 g 4 d 4 h ### 1 1 から9 9 までの組を出力 (途中は sed で削除) $ join -j9 <(seq 9) <(seq 9) | sed 4,78d 1 1 1 2 1 3 9 7 9 8 9 9 awk '$0=a+=$1*$2' -> 累積和を計算しています。 参考: awk 基礎 ### a に累積和を保存 $ seq 4 | awk '{a+=$1; print a}' 1 3 6 10 ### (a+=$1) は代入後の a の値に評価される $ seq 4 | awk '{print a+=$1}' 1 3 6 10 ### $0 に a を代入し、間接的に a を print ### pattern = $0=(a+=$1), action = 暗黙的に {print $0} となる $ seq 4 | awk '$0=(a+=$1)' 1 3 6 10 tail -n1 -> 最後の1行を抽出しています。 Q2( @butackle66 さんから) 9132円の支払いに10000円札で払ったときのおつりの出し方をひとつ(できるひとはたくさん)画面に出力してみてください。 https://twitter.com/ryuichiueda/status/1629340985389125632?s=20 解答例1 文字数が少ないです。 また、出すコインの枚数が最小になります。 $ printf %s\\n 500 100 50 10 5 1|awk 'BEGIN{t=10000-9142}{t-=$0*(a=int(t/$0))}$0=$0" "a' 500 1 100 3 50 1 10 0 5 1 1 3 解説 printf %s\\n 500 100 50 10 5 1 -> 6種類のコインを出力しています。 ### `printf` は第1引数のフォーマットを第2引数以降の全てに適用する $ printf '%s\n' 500 100 50 10 5 1 500 100 50 10 5 1 $ printf '%s/' 500 100 50 10 5 1 500/100/50/10/5/1/ awk 'BEGIN{t=10000-9142}{t-=$0*(a=int(t/$0))}$0=$0" "a' -> 以下の python3 コードをワンライナーにしています。 参考: awk 基礎 t = 10000 - 9142 for d0 in [500, 100, 50, 10, 5, 1]: # $0 a = int(t / d0) # a=int(t/$0) t = t - d0 * a # t-=$0*(...) d0 = str(d0) + " " + str(a) # $0=$0" "a print(d0) 解答例2 ネタです。 コインの枚数の組み合わせを金額が858円になるまでランダムに探索しています。 $ tr -dc 0-9</dev/urandom|fold -w6|awk -F '' '500*$1+100*$2+50*$3+10*$4+5*$5+1*$6==10000-9142'|head -n1 071928 出力の見方: 500円玉0枚, 100円玉7枚, ..., 1円玉8枚 解説 tr -dc 0-9</dev/urandom|fold -w6 -> 6桁のランダムな数字を生成しています。 ### /dev/urandom はランダムなデータを返す $ cat /dev/urandom | head -c10 ȱ�KH$繀 ### tr -d: 削除, -c: 以外を -> 0-9 以外を削除 -> 0-9 のみ抽出 $ cat /dev/urandom | tr -dc 0-9 | head -c10 0597488882 ### fold で6桁に切り詰める $ cat /dev/urandom | tr -dc 0-9 | fold -w6 | head -n3 764711 039900 491372 awk -F '' '500*$1+100*$2+50*$3+10*$4+5*$5+1*$6==10000-9142' -> 入力の6桁の数字のうち金額が858円となるものを抽出しています。 ### -F '' で文字単位にフィールド分割 $ echo abc | awk -F '' '{print $2}' b また、pattern/action が以下となります。 pattern: 500*$1+100*$2+50*$3+10*$4+5*$5+1*$6==10000-9142 action: 暗黙的に {print $0} Q3 次のLaTeXの原稿で、\labelと\refがペアになっていない(参照していない/されていない)ものを抽出してください。 https://twitter.com/ryuichiueda/status/1629347379219689473?s=20 題意としては、参照されていない図表、図表の無い参照を見つけたいということです。 また、今回は \label が0個で \ref が2個のように片方が0個で片方がn個のようなシチュエーションの考慮は不要です。 参照されていない図表の例 \label{hoge} はあるが \ref{hoge} がない 図表のない参照の例 \ref{hoge} はあるが \label{hoge} がない 解答例1 文字数が少ないです。 $ grep -oP '(label|ref)\K[^}]*' genkou.tex|sort|uniq -u {eq:state_equation_linear {eq:state_equation_nonlinear {fig:typhoon 解説 grep -oP '(label|ref)\K[^}]*}' genkou.tex -> ファイルから \label{...} や \ref{...} の部分を抽出しています。 ### -o: マッチした部分のみ抽出 $ echo 12344321 | grep -o .3 23 43 ### lable または ref から } が現れる直前までを抽出 (-P は後述) $ grep -oP '(label|ref)[^}]*' genkou.tex | head -n3 label{eq:state_transition_model label{eq:state_transition_model2 ref{eq:state_transition_model ### -P: perl 拡張正規表現を利用 -> \K と | を使うため ### \K: \K より左をマッチとして出力しない ### ..3 にマッチさせて... $ echo 12344321 | grep -oP '..3' 123 443 ### 最初の1文字を出力したくない場合 $ echo 12344321 | grep -oP '.\K.3' 23 43 ### \K を使って label と ref を出力から除外 $ grep -oP '(label|ref)\K[^}]*' genkou.tex| head -n3 {eq:state_transition_model {eq:state_transition_model2 {eq:state_transition_model sort|uniq -u -> 重複のないユニークな行のみを抽出しています。 ### サンプルデータ $ seq 1 3; seq 2 4 1 2 3 2 3 4 ### ユニークな行のみを抽出 ### なお、uniq は直前の行との比較のみを行うので、あらかじめ sort が必須 $ (seq 1 3; seq 2 4) | sort | uniq -u 1 4 uniq の便利なオプション ### 重複排除 $ (seq 1 3; seq 2 4) | sort | uniq 1 2 3 4 ### ユニークな行のみを抽出 (再掲) $ (seq 1 3; seq 2 4) | sort | uniq -u 1 4 ### 重複した行のみを抽出 $ (seq 1 3; seq 2 4) | sort | uniq -D 2 2 3 3 ### 重複した行のみを抽出し、重複排除して出力 $ (seq 1 3; seq 2 4) | sort | uniq -d 2 3 ### 重複個数をカウント $ (seq 1 3; seq 2 4) | sort | uniq -c 1 1 2 2 2 3 1 4 解答例2 勉強会で解説された方法です。 sort と uniq のフィールド機能を活用しています。 $ cat genkou.tex | grep -oE '(ref|label){.*}' | tr '{}' ' ' | sort -k2,2 | uniq -u -f1 label eq:state_equation_linear label eq:state_equation_nonlinear ref fig:typhoon 解説 ### `grep -oE` までは基本的に解答例1と同じ。| を使うために -E を指定 $ cat genkou.tex | grep -oE '(ref|label){.*}' | head -n3 label{eq:state_transition_model} label{eq:state_transition_model2} ref{eq:state_transition_model} ### tr で { と } をスペースに変換 $ cat genkou.tex | grep -oE '(ref|label){.*}' | tr '{}' ' ' | head -n3 label eq:state_transition_model label eq:state_transition_model2 ref eq:state_transition_model ### sort -k2,2 で2フィールド目で並べ替え $ cat genkou.tex | grep -oE '(ref|label){.*}' | tr '{}' ' ' | sort -k2,2 label eq:state_equation_linear label eq:state_equation_nonlinear label eq:state_transition_model ref eq:state_transition_model ref eq:state_transition_model ref eq:state_transition_model label eq:state_transition_model2 ref eq:state_transition_model2 ref eq:state_transition_model2 ref eq:state_transition_model2 ref eq:state_transition_model2 ref eq:state_transition_model2 ref eq:state_transition_model2 label fig:motion ref fig:motion ref fig:typhoon ### uniq -u -f1 で1フィールド目を無視してユニークな行を抽出 $ cat genkou.tex | grep -oE '(ref|label){.*}' | tr '{}' ' ' | sort -k2,2 | uniq -u -f1 label eq:state_equation_linear label eq:state_equation_nonlinear ref fig:typhoon Q4 次のファイルから、「東西南北」がワンセットになっている部分を探してください。東西南北の順番は問いません。 https://twitter.com/ryuichiueda/status/1629355288145920001?s=20 例: 東西南北西東 という入力文字列から 1 東西南北 , 3 南北西東 を出力する。 何文字目から始まるかの情報も出力する。 解答例1 愚直に解くとこうなるでしょう。 $ awk '{while(a=substr($0,++i,4)){print i,a}}' tonnan.txt|awk '/東/*/西/*/南/*/北/' 3 西南北東 12 南北西東 33 南東西北 41 西北南東 43 南東北西 54 東北南西 55 北南西東 56 南西東北 57 西東北南 58 東北南西 63 南西東北 64 西東北南 68 東南西北 71 北西南東 72 西南東北 97 西北東南 awk '{while(a=substr($0,++i,4)){print i,a}}' -> 入力文字列を4文字ずつ切り出しています。 a=substr($0, ++i, 4) で a (東西南北の文字列) と i (インデックス) を以下のように設定しています。 南西西南北東東西東... (入力) 南西西南 == a, i == 1  西西南北 == a, i == 2   西南北東 == a, i == 3    南北東東 == a, i == 4     北東東西 == a, i == 5      東東西東 == a, i == 6 実行結果 $ awk '{while(a=substr($0,++i,4)){print i,a}}' tonnan.txt | head -n5 1 南西西南 2 西西南北 3 西南北東 4 南北東東 5 北東東西 awk '/東/*/西/*/南/*/北/' -> 東西南北が4つとも含まれている行のみ抽出しています。 pattern の /regex/ は $0 ~ /regex/ と等価です。 すなわち、入力行を正規表現マッチした結果を 0 or 1 の数値に評価します。 よって、以下の式は等価です。 /東/*/西/*/南/*/北/ ($0 ~ /東/) && ($0 ~ /西/) && ($0 ~ /南/) && ($0 ~ /北/) 解答例2 eban さんの解答 を解説します。 文字数が少ないです。 「東西南北が含まれている」の判定を「1行に同じ文字が2回出現しない」で行っています。 $ sed ':a;p;s/.//;ta' tonnan.txt|grep -o ^....|grep -vnE '(.).*\1' 3:西南北東 12:南北西東 33:南東西北 41:西北南東 43:南東北西 54:東北南西 55:北南西東 56:南西東北 57:西東北南 58:東北南西 63:南西東北 64:西東北南 68:東南西北 71:北西南東 72:西南東北 97:西北東南 解説 sed ':a;p;s/.//;ta' -> パターンスペースの1文字目の削除と print を繰り返しています。 パターンスペースの解説はここではやりませんが、tonnan.txt の中身で初期化された文字列型の変数と考えて下さい。 この sed は以下4つのコマンドから成り立っています。 :a # ラベル p # パターンスペースを print s/.// # パターンスペースの最初の1文字を削除 ta # 直前の置換が成功したら :a に戻る 実行結果 $ sed ':a;p;s/.//;ta' tonnan.txt | tail 東南東東西北東南 南東東西北東南 東東西北東南 東西北東南 西北東南 北東南 東南 南 grep -o ^.... -> 先頭4文字を切り出しています。 $ sed ':a;p;s/.//;ta' tonnan.txt | grep -o ^.... | tail 南東東北 東東北南 東北南東 北南東南 南東南東 東南東東 南東東西 東東西北 東西北東 西北東南 grep -vnE '(.).*\1' -> 同じ文字が含まれていない行のみを抽出しています。 \1 は (.) と同じ文字にマッチします (後方参照といいます)。 例えば (.)(.)(.)\3\2\1 は abccba にマッチします。 ### (.).*\1 -> 同じ文字が少なくとも2文字含まれている行 ### -v -> 以外を ### -n -> 行番号付きで表示 ### -E は () を使うために指定 $ sed ':a;p;s/.//;ta' tonnan.txt|grep -o ^.... | grep -vnE '(.).*\1' 3:西南北東 12:南北西東 33:南東西北 41:西北南東 43:南東北西 54:東北南西 55:北南西東 56:南西東北 57:西東北南 58:東北南西 63:南西東北 64:西東北南 68:東南西北 71:北西南東 72:西南東北 97:西北東南 Q5 つぎのリバーシの盤面について、E6とG7に❌を打ちたいです。 $ cat reversi.txt  12345678 A         B         C   ⚪     D   ⚪⚫    E   ⚫⚫    F    ⚪⚫   G         H         つぎのワンライナーを完成させてください。(Perl使える人もこの問題はsedでお願いします。) $ cat reversi.txt | sed あるsedのコード | sed 最初のsedのコードと同じコード https://twitter.com/ryuichiueda/status/1629363762879758336?s=20 題意はこんな感じです。 sed でリバーシの実装をしたい。 ⚪ が置ける場所に印をつけたい。 今回のスコープは左斜上に探索して判定する処理。 期待する出力  12345678 A         B         C   ⚪     D   ⚪⚫    E   ⚫⚫❌   F    ⚪⚫   G      ❌  H         解答例 勉強会で解説されたものです。 別解の余地は無いでしょう。 $ cat reversi.txt | sed -Ez 's/(⚪.{10}(⚫.{10})+) /\1❌/g' | sed -Ez 's/(⚪.{10}(⚫.{10})+) /\1❌/g'  12345678 A         B         C   ⚪     D   ⚪⚫    E   ⚫⚫❌   F    ⚪⚫   G      ❌  H         解説 -z オプションで改行を無視しています。 C 言語で2次元配列を扱うのと同じ要領で探索しています。 reversi.txt の横幅が10文字であることを利用しています。 こんなパターンを見つけたら全角スペースを❌に変換しています。 ⚪(任意の10文字)⚫(全角スペース) ((任意の10文字)⚫) を+で繰り返すことで、⚫が2個続くパターンを検知 sed -z の実行例 $ printf 'abc\ndef\nghi' abc def ghi ### & はマッチ全体 -> マッチした部分の後ろに x を追記 $ printf 'abc\ndef\nghi' | sed -z 's/b.../&x/' abc dxef ghi Q6 Q5のワンライナーに続けて、⚪と❌の間の⚫を⚪にしてください。 https://twitter.com/ryuichiueda/status/1629369189843750912?s=20 解答例 勉強会で解説されたものです。 合理的な別解は恐らくありません。 $ cat reversi.txt | sed -Ez 's/(⚪.{10}(⚫.{10})+) /\1❌/g' | sed -Ez 's/(⚪.{10}(⚫.{10})+) /\1❌/g' | tr -d '\n' | perl -CSD -Mutf8 -pe 's/⚫(?=.{9}(⚫.{9})*❌)/⚪/g' | fold -b27  12345678 A         B         C   ⚪     D   ⚪⚪    E   ⚫⚪❌   F    ⚪⚪   G      ❌  H         解説 perl を使いますが、改行無視は sed -z のような感じではなく、tr と fold で行っています。 perl -CSD -Mutf8 -pe 's/⚫(?=.{9}(⚫.{9})*❌)/⚪/g' -> コンセプトは Q5 とだいたい同じ。 -CSD -Mutf8 -> マルチバイト対応 ⚫(?=.{9}(⚫.{9})*❌) -> (?=...) は肯定先読み。 検索対象だがマッチしたとはみなさない。 今回の場合、検索対象だが置換対象でなくなる。 -> 先頭の⚫のみが⚪への置換対象となる。 LT LT ( Long Talk) が2件ありました。 LT1 curlでTelegram botを操作 (やべえ @yabeenico ) Twitter YouTube 東京会場から 私が登壇しました。 初めてのシェル芸勉強会での LT 発表でした。 チャットアプリ Telegram を curl で操作する方法を紹介しました。 LT2 音声合成してみよう (たいちょー @xztaityozx_001 ) Twitter YouTube 大阪サテライトから 自作初音ミクみたいなことをしていました。 たいちょーさんの音声を返す REST API エンドポイントが立ち上がっていたので、みんなでたいちょーさんの音声を合成しました。 おわりに Q1~Q4 は試行錯誤できてが楽しかったです。 Q5 Q6 は難しいのに加えて別解の余地がなかったので厳しかったです。 シェル芸勉強会の問題を解くときは、厳密な解答より、シチュエーションをイメージしてそれにマッチするものを考えると良いです。 例えば Q3 では、私は次のシチュエーションをイメージしました。 「図表の参照のアンマッチが存在することを知ったのでとりあえずその一覧が欲しい」 そのため、Q3 解答例1では { が先頭に残っていたり、 ref と label を区別できませんが、シチュエーションにマッチするのでそれでいいのです。 また、勉強会でも言及されていますが、問題が解けなくても解くまでに試行錯誤したこと自体に学びがあります。 問題を解くまでのプロセスを大切にしていきましょう。 以上、第63回ビッグハンドタウンシェル芸勉強会の問題と解説でした。
アバター
1.はじめに このブログを読んで下さっている皆様、こんにちは。 プラットフォームサービス本部 クラウド&ネットワークサービス部 開発オペレーション部門の丹野(入社10年目)です。 この記事では、NTTコミュニケーションズに入社してから業務内容の変化に適応するため、必要なスキルを獲得してきた私の体験(リスキリング体験)と、最近自分で立ち上げて運営している社内での育成の取り組みについて紹介します。 2.私のスキル習得の体験(リスキリング体験) 最初に私の入社してからの業務経歴を簡単に紹介します。 ①入社1~3年目は、プリセールスエンジニアという立場でグローバルネットワークサービスを販売する営業組織を支援。 ②入社4~7年目は、 Software Defined Networking のサービスオーダーからお客様の利用開始までのプロセス構築と運用(以下、SOデリバリと呼ぶ)の立上げ。  当時、Software Defined Networkingというサービスを世に出すためのプロジェクトが立ち上がった頃に、私は参画しました。  サービス全体のプロジェクトの中でSOデリバリに関する部分を任され、プロジェクトリーダー的な仕事もこなしていました。 ③入社8~10年目は、リリースしたサービスのSOデリバリプロセスの維持運用と改善や、部内サービスの課題発掘とその改善のためにUX/UIデザインやデータサイエンスのスキルを身に付けて業務で活かしています。 上記より、私の業務アサインメントの変化に伴い、業務で求められるスキルの軸が多岐に渡って変わってきているというイメージが湧いてくるのではないかと思います。 (①ネットワークや英語⇒②プロジェクトマネジメント⇒③UI/UXデザインやデータサイエンス) このように、会社や業務で求められるスキルは多岐に渡ります。 特にUI/UXデザインやデータサイエンスについては、NTTコミュニケーションズでもここ2~3年の間で、これからのサービス開発や販売に必要なスキルとして力を入れている分野になります。 UI/UXデザイン分野での取り組み事例として上げられるのは、 デザインスタジオ KOEL が NeWork というサービスに対し、ユーザーリサーチ/コンセプトの仮説検証/プロトタイプ/ユーザーテスト等に対しデザインスキルを十二分に発揮して貢献している事例があります。 その他の活躍も幅広く、 リンク先の事例 を是非ご覧ください。 また、データサイエンス分野での取り組み事例として上げられるのは、全社でのデータ活用を促進することを目指すデータドリブンマネジメント推進部門(以下、DDM推進部門)です。 DDM推進部門では、全社のデータを集約して活用できるデータマートや分析基盤の構築と、データを分析して社内の課題解決に生かすコンサル業務とデータ分析できる人材の育成をしています。 本記事のポイントとして会社や上述のような組織や一緒に働く仲間からもしっかりサポートがあり 、そのサポートを私も受けて今に至っています。 例えば、社内OJTという制度があり、自身が手を挙げるとDDM推進部門等の専門性の高い組織へ行き、仕事を通し専門スキルを身に着ける制度があります。 OJTとして、専門性の高い組織で新分野にどっぷり浸かり十分なスキルを身に着けた上で自部署の業務に戻り、身につけた力を発揮できます。 さらに、OJT等の社内制度とは関係なく、 部署を超えて社員同士がデザインやデータ活用について自発的に取り組み、ノウハウの横展開やスキルアップが盛んに行われています。 (以前、本ブログで紹介した 分析コンペ もその1つです) 私自身も、様々な新しいチャレンジや業務を始めるにあたり、自分自身のスキル転換に悩むこともありました。 しかし、結論だけ言うと業務における幅も広がったので、スキル転換をして良かったと思います。 部内チームや社内OJTを通してスキル転換を図りつつ、色々な人と出会うことができ、身につけたスキルを今の仕事に活かすことができています。 そして、後述する新しいチャレンジに取り組みつつ得たスキルを業務で活かしながら、社外資格も取る事で社内外的に知識や業務経験があることを証明できたと実感しています! 例えば、 ・ Python3エンジニア認定基礎試験 ・ Python3エンジニア認定データ分析試験 ・ 人間中心設計スペシャリスト です。 NTTコミュニケーションズでは 認定された社外資格に対し資格取得の支援制度があります。 認定された資格に合格すると会社から受験料の支援があるのでチャレンジしやすい環境にあります。 さらに、会社から研修受講/団体登録/受験といった資格維持や更新のために必要な費用の負担等を支援してもらえます。 3.社内での育成の取り組み 私自身の体験も活かしつつ、自部の仲間たちのリスキリングにも貢献したいと思い、2021年10月から私の所属する部内でデータ分析人材の育成を開始しました。 このデータ分析人材の育成プログラムは習熟度により現時点でSTEP1/STEP2/STEP2.5と段階を分けています。 STEP1とSTEP2.5は、私を含めた社員による内製プログラムで研修を企画開催しています。 STEP2では、社内の事例共有会でデータ分析を役立てている事例発表会を開催し、幹部含めて実務でデータ分析を活用しているベストプラクティスを紹介して受講社員のインプットだけではなくアウトプットする機会を作り、スキルの定着や周りの社員達の仲間づくりを促進しています。 STEP1の内製研修は, CRISP-DM を基に、 ・データ分析へ至るための設計方法 ・データ分析で用いる統計基礎や手法の使い方と注意点 ・データ分析結果の表現(バイアスや伝え方の注意点) を実施し、第1~11回で、延べ431名の方に受講いただきました。 STEP1の狙いとして、データ分析の初心者向けにデータ分析プロジェクトの各フェーズでの考え方や統計基礎といった基礎力身に付けてもらうこと です。 業務でプロジェクトをこなす、または参加する際にどう進めればよいか戸惑うことがなくなり、プロジェクトで出てくる統計値の見方や気にすべきポイントが分かることを目指したプログラムにしました。 社内研修なので多少内部バイアスがかかっているかもしれませんが、下図の CSポートフォリオ分析 から、どの項目も満足度が5点満点中4以上となっています。 特に受講者の方からは内容について好評をいただきました。 この内製研修により受講頂いた方々も新しいスキルに挑戦でき、データ分析プロジェクトに対する戸惑いのハードルを乗り越えられると感じて頂けたのではないかと分析しています。 この内製研修を実施したことで部内でのデータ活用が更に広がる機会の1つになったと思います。 現在は、STEP1で基本的なデータリテラシ知識を習得した受講者の方も含め2022年10月からSTEP2.5として、Pythonによるデータ加工/前処理や機械学習を取り入れた「データ分析内製研修-シーズン2-」を企画開催しています。 「データ分析内製研修-シーズン2-」では、データ加工/前処理や機械学習の基本知識はもちろん、 社内で業務効率化や判断の適正化に繋がる題材(例:アソシエーション分析/退会予測/線形回帰モデルによる予測 等)のハンズオンも取り入れて業務に応用しやすいプログラム を組んでいます。 さらに、 実際の社内業務で使っているデータも取り扱っているため、業務への成果とスキルが直結する実感を得てもらえる ように取り組んでいます。 今後も、育成の取り組み等を通じて各部と連携しながら、組織全体の学習の最大化へ貢献できる活動をしていきたいです。 4.最後に このように、NTTコミュニケーションズでは、スキル習得に対する様々な取り組みや環境整備がなされています。 入社してから10年の間で、仕事で求められるスキルが変わり、国や社会でも求められている事も刻々と変化しています。 時代で求められるスキルや知識の変化に適応していくことも重要 だと感じます。 これからも私自身コミュニティ等を通じリスキリングをしていきつつ、学んだことを業務で活かしたり、内製研修の企画開催やコミュニティ運営という形で学んだことを社内に還元していきます。 また、社内事例の紹介では、事例記事を書いて社内ポータルへの掲載やライトニングトーク会を開催し事例のシェアを行い、スキルの具体的な活用方法を広める活動も継続していきます。 最近、文部科学省が出している 「情報Ⅱ」の教員研修用教材 を読みました。私の高校時代には無かった科目です。 内容を確認してみると、私の大学時代に履修した内容を既に高校生の時点で取り組んでいました。 国や社会のITに対する期待が高く、今の若い世代は我々の世代と比べて新しいスキルを持って社会に出てくるのだと改めて実感し、 10年後や20年後には、会社や社会で求められていくITスキルやIT知識のベースラインが変わってくる という事を肌で感じました。 今まで以上にリスキリングへ取り組み、これからも時代についていけるよう、もっと日々精進をしなければいけないと私自身、気合が入ります。 これからも会社の皆と一緒に研修やコミュニティなどを通じ、新しいスキルを身に付けて、それを役立てるように私自身も頑張っていきます。
アバター
目次 目次 はじめに ECCV2022のトラッキング論文 ピックアップした論文 Towards Grand Unification of Object Tracking Tracking Objects As Pixel-Wise Distributions Particle Video Revisited: Tracking through Occlusions Using Point Trajectories XMem: Long-Term Video Object Segmentation with an Atkinson-Shiffrin Memory Model 最後に はじめに この記事は前回の記事の後編です。ECCV2022で紹介されたトラッキングに関する論文をいくつかご紹介します。 engineers.ntt.com ECCV2022のトラッキング論文 1645本の論文の中で"tracking"や"tracker"という単語がタイトルに含まれているものは、以下の表に示した通り30件ありました。 これに限らず、Video Segmentationなどの文脈で物体追跡をおこなっている論文もあるようでした。 2次元物体追跡 論文タイトル リンク ⭐️ Towards Grand Unification of Object Tracking abst GitHub ⭐️ Tracking Objects as Pixel-wise Distributions abst GitHub ⭐️ Particle Video Revisited: Tracking Through Occlusions Using Point Trajectories abst GitHub ⭐️ XMem: Long-Term Video Object Segmentation with an Atkinson-Shiffrin Memory Model abst GitHub AiATrack: Attention in Attention for Transformer Visual Tracking abst GitHub Joint Feature Learning and Relation Modeling for Tracking: A One-Stream Framework abst GitHub Backbone is All Your Need: A Simplified Architecture for Visual Object Tracking abst GitHub Hierarchical Feature Embedding for Visual Tracking abst GitHub MOTR: End-to-End Multiple-Object Tracking with TRansformer abst GitHub Tracking Every Thing in the Wild abst Bayesian Tracking of Video Graphs Using Joint Kalman Smoothing and Registration abst ByteTrack: Multi-Object Tracking by Associating Every Detection Box abst GitHub Robust Multi-Object Tracking by Marginal Inference abst Towards Sequence-Level Training for Visual Tracking abst GitHub Tracking by Associating Clips abst Graph Neural Network for Cell Tracking in Microscopy Videos abst GitHub Robust Visual Tracking by Segmentation abst GitHub FEAR: Fast, Efficient, Accurate and Robust Visual Tracker abst GitHub HVC-Net: Unifying Homography, Visibility, and Confidence Learning for Planar Object Tracking abst Robust Landmark-based Stent Tracking in X-ray Fluoroscopy abst 3次元物体追跡 論文タイトル リンク CMT: Context-Matching-Guided Transformer for 3D Tracking in Point Clouds abst Towards Generic 3D Tracking in RGBD Videos: Benchmark and Baseline abst GitHub SpOT: Spatiotemporal Modeling for 3D Object Tracking abst 3D Siamese Transformer Network for Single Object Tracking on Point Clouds abst GitHub Large-displacement 3D Object Tracking with Hybrid Non-local Optimization abst GitHub PolarMOT: How far can geometric relations take us in 3D multi-object tracking? abst project その他 論文タイトル リンク 概要 MOTCOM: The Multi-Object Tracking Dataset Complexity Metric link project データセットの難易度評価 The Fish Counting Dataset: A Benchmark for Multiple Object Tracking and Counting link GitHub 新しいデータセットの提案 Large scale Real-world Multi Person Tracking link GitHub 新しいデータセットの提案 BodySLAM: Joint Camera Localisation, Mapping, and Human Motion Tracking link video 画像から人物の姿勢、3Dメッシュなどを推定 AvatarPoser: Articulated Full-Body Pose Tracking from Sparse Motion Sensing link GitHub 体につけた少数のVRトラッカから全身の姿勢を推定 ピックアップした論文 以下では、リストアップした中で私たちが特に興味を持った4つの論文について詳細に見ていきます。 Towards Grand Unification of Object Tracking 概要 シングルカメラのトラッキングには似て非なる以下の4つのタスクがあります。本論文ではこれらのいずれにも適用可能なモデルが提案されています。 Single Object Tracking (SOT) 動画の冒頭にユーザーがバウンディングボックスで指定した単一の物体を追跡するタスク。カテゴリの指定が無く半教師的な推論を必要とする。 サーベイ論文: Single Object Tracking: A Survey of Methods, Datasets, and Evaluation Metrics 関連データセット: TrackingNet 1 , LaSOT 2 Multiple Object Tracking (MOT) 特定のカテゴリに属する複数の物体をバウンディングボックスで追跡するタスク。車両のトラッキングなど、同じカテゴリで似通った物体を混同せずに追跡する必要がある。 関連データセット: MOT17 3 , BDD100K 4 Video Object Segmentation (VOS) 動画の冒頭にユーザーがセグメンテーションマスクで指定した物体を追跡するタスク。 関連データセット: DAVIS(Densely Annotated Video Segmentation)-2016 & DAVIS-2017 5 Multi Object Tracking and Segmentation (MOTS) 特定のカテゴリに属する複数の物体をセグメンテーションマスクで追跡するタスク。 関連データセット: MOTS20 Challenge 6 , BDD100K MOTS Challenge 研究のモチベーション 著者の問題意識として以下の2点が挙げられています。 現状最新の手法は特定のタスクに特化しすぎて汎用性に欠けている 独立してモデル設計をしているため、パラメーターが冗長化している 上記のタスクを同一のネットワークで解くことにより、これらを解決しようとしています。 関連研究 以下のようにSOTとVOTを同時に解ける手法や、MOTとMOTSを同時に解ける手法などがこれまで提案されてきましたが、4つのタスクを全て同時に解く手法は本論文が初めてだと主張しています。 SOTとVOTを同一ネットワークで解いた研究 D3S-a discriminative single shot segmentation tracker 7 , In CVPR2020 Siam R-CNN: Visual Tracking by Re-Detection 8 , In CVPR2020 MOTとMOTSを同一のネットワークで解いた研究 TrackFormer: Multiobject tracking with transformers 9 , In CVPR2021 Track to detect and segment: An online multi-object tracker 10 , In CVPR2021 提案手法 以下の図に示したネットワークはUnicornと名付けられています。まず初めに、Reference FrameとCurrent Frameの両方を解像度そのままでFPN(Feature Pyramid Network)に入力し、次に説明するUnified EmbeddingやUnified Headに渡します。 Unified Embedding このコンポーネントでは特徴マップからフレーム間をピクセルレベルで対応付けます。 まず図中のI(Interaction)の部分ではVision Transformer (ViT)を用いることで、タイムスタンプが離れているReference FrameとCurrent Frameに対して正確な対応関係を抽出します。単なるViTでは計算量が増えてしまうので、Deformable Attention Transformerと呼ばれるAttention範囲を自由に変形しながら効率よく重要な領域を参照するモデルを使っています。 Unified Head 上で得られた対応関係はタスクによって異なる活用がなされます。 SOTとVOSでは図中のP(Propagation)の部分で、ユーザーがReference Frameに与えたセグメンテーションマスクから(バウンディングボックスが与えられた場合は四角いマスクとみなし同様に処理します)、計算したピクセルの対応関係に基づいて、Current Frameにおいて物体のありそうな位置の事前分布を計算します。この事前分布は物体検出モジュールであるUnified Headの直前に適用され、画像の注目すべき箇所を示しています。 一方、MOTとMOTSでは物体検出後の図中のA(Association)の部分で、ピクセルレベルの対応関係をもとにインスタンスレベルでのオブジェクト同士の紐付けを行い、追跡中のターゲットとCurrent Frameから検出した物体の対応づけを行います。 このようにひとつの出力を4つのタスクそれぞれに流用できることがこの手法の特長です。 学習方法 全体の学習方法はSOT-MOT合同学習とVOS-MOTS合同学習の2つの段階で行われます。初めにSOT-MOT合同学習では、SOTとMOTのデータセットからエンドツーエンドにUnified Embeddingのピクセルレベルの対応損失とUnified Headの検出損失を用いてネットワークを最適化します。次のVOS-MOTS合同学習では、マスクブランチ 11 , 12 を追加して他のパラメータを固定してVOSとMOTSのデータセットを用いてマスク損失を最適化することで学習します。 実験結果 学習には16台のNVIDIA Tesla A100で回しています。論文には書かれていませんでしたが、推論もおそらく学習時と同じ環境を使っていると思われます。 各タスクそれぞれについて専門的に学習したモデルと同程度の性能を持ち、A100で入力解像度640x1024に対し20FPSというリアルタイム推論も達成しています。 感想 本来4つのモデルが必要であるところを1つのモデルで完結させることができ、トレーニングコストの面でも推論コストの面でも有用だと言えます。また、異なるタスクを単一のモデルに解かせることで、汎化性能の高いモデルが獲得できているのではないかとも感じました。 Tracking Objects As Pixel-Wise Distributions 概要 この論文では、Multi-object tracking (MOT)においてバウンディングボックスや中心点による従来のトラッキング手法と異なり、オブジェクト位置の分布をピクセル単位に推定してトラッキングする手法P3AFormerを提案しています。ピクセル単位に推定することで小さなオブジェクトにオクルージョンが発生した場合も正しくトラッキングすることが可能になります。 提案手法 次の図は、提案手法のオブジェクト位置の分布をピクセル単位で予測する方法を示しています。まず、ResNetやSwin-Transformer 13 などのBackboneが用いられ画像特徴を抽出します。そこからPixel DecoderにはDETR 14 のdecoderが用いられ、アップサンプリングを適用することでピクセル単位の表現を生成します。次に、フロー間の特徴をまとめるためにFlowNet 15 , 16 を用いて前フレームからピクセル単位の特徴伝搬(Pixel-wise Propagation)を行います。最終的にTransformer Decoderを用いてピクセル単位のオブジェクト位置の分布を予測します。 次の図は、ピクセル単位で推定されたオブジェクト位置の分布を用いてフレームごとの関連付けを行う概要です。基本的な枠組みはカルマンフィルター 17 とハンガリアンアルゴリズムを利用する一般的な方法と同様です。ハンガリアンアルゴリズムのコスト関数は式(6)のように定義されています。式(6)の右辺括弧内が、これまでの時刻で追跡されたオブジェクト と時刻 で検出された候補オブジェクトiに定義されるコストです。 は予測クラスのクロスエントロピー誤差です。 はピクセルのヒートマップを表しており、真のヒートマップ はオブジェクト中心に位置するように設定されたガウス関数が用いられます。ガウス関数の半径はオブジェクトの大きさに比例するように設定されます。つまり式(6)は、既に追跡されたオブジェクト集合と時刻 で新たに検出された候補オブジェクト集合の総当たりペアのうち、予測されたクラスラベルが正しくかつヒートマップの重なりが大きいものが同一物体として対応付けられるよう設計された関数であると言えます。 実験結果 次の図は(a)MOTR 18 , (b)TransCenter 19 , (c)提案手法のP3AFormerのトラッキング結果を示しています。画像右上に小さく表示されている人物に注目してください。(a)は小さな人物の検出に失敗しています。(b)は青枠で示しているようにオクルージョンが発生した場合IDが誤って割り当てられます。これに対して(c)の提案手法はピクセル単位のオブジェクト位置の推定によりオクルージョン下においても小さな人物を頑健にトラッキングしていることが分かります。 次の図はP3AFormerのピクセル単位で推定した中心ヒートマップとトラッキング結果を可視化しています。オブジェクトの中心は全貌が大きく隠れた場合でも正しく推定できており、結果として隠れに頑健な追跡につながっていることが分かります。 感想 トラッキングにピクセル単位の推定を用いることでオクルージョンが頑健になり性能を向上させている点が面白かったです。 Particle Video Revisited: Tracking through Occlusions Using Point Trajectories 概要 任意のピクセルをターゲットとしてトラッキングを行う論文です。この手法をベースに、ユーザーに指定されたセグメンテーションマスクのそれぞれのピクセルを追跡することでVOTタスクを解くことができます。同じくピクセルレベルの密なトラッキングが可能なoptical flowよりもオクルージョンに強く、長いフレームにわたって対象を追跡できるという特長があります。 背景 モーション推定のアプローチは2通りに分けられます。 feature matchingで特徴点の対応をとる 離れたフレーム間でも対応可能 推定が疎であり、狙ったピクセルのトラッキングができない optical flowで各ピクセルのモーションベクトルを得る optical flowの計算を繰り返せば狙ったピクセルをトラッキングできる あくまで局所的なピクセルの変化しか追えない 追跡対象の手前を何かが通り過ぎた時、手前の物にトラッカーが移るなどして破綻する 追跡中のピクセルが少しずつずれていく そしてこの2つのアプローチの中間をとった2006年のparticle video 20 では、画面全体にトラッカーを配置しフレームごとにお互いの位置関係を調整させることで、feature matchingよりも密でoptical flowよりも長期間安定したモーション推定が可能になりました。 しかしその手法は依然としてオクルージョンに対応しきれておらず、追跡対象が隠れたことは検知できるもののそのまま見失ってしまうという問題がありました。 本論文ではそれをディープラーニングベースに拡張し、任意のピクセルを独立に追跡できるうえ、一時的なオクルージョンなら再追跡ができるようになっています。 提案手法 フレームの連続画像と、トラッキングしたい点(トラッカーと呼びます)の初期座標を入力として、各フレームにおけるトラッカーの座標と、トラッカーが隠れているかどうかを示すオクルージョンフラグを推定します。原理的には追跡対象が隠れても フレーム以内にまた現れれば見つけ出せることが期待できます。 全体設計はoptical flow推定器のRAFT 21 に似ており、更新関数を繰り返し適用して次のフレームの予測値を収束させるという方法をとっています。 まずRAFTと同じネットワークを利用し、各フレーム独立に特徴マップ(feats)を作成します。 次に初期フレームでの座標 とその位置の特徴 を用意し、 フレームにわたる予測値を全てそれらに初期化します( )。これはちょうどトラッカーが フレームの間全く動いていないと仮定していることになります。 そしてIterative inferenceで囲われている処理によって各時刻におけるトラッカーの座標 , 特徴 , オクルージョンフラグ が少しずつ最適化されていき、 回のイテレーションの後に得られた予測値 を最終的な予測とします。 Iterative inferenceではイテレーションごとに次のような操作が行われます。初めに、現状のトラッカーの推定特徴 と特徴マップfeatsとのdot productをとり、トラッカーと入力画像との類似度マップを生成します。次に、類似度マップからトラッカーの推定位置 の周り の範囲を切り出します。この時切り出す解像度は 種類用意しており、1つのトラッカーに対して のshapeを持つテンソルが手に入ります。 そして、ナイーブな手法を考えるならその中で最大値を取る(つまり最も類似度の高い)点が一番もっともらしいトラッカー位置だと言えますが、本手法ではMLP Mixerと呼ばれるニューラルネットを用いてトラッカーの位置 , 特徴 , オクルージョンフラグ を推定します。以上の操作を繰り返すことで予測を少しずつ最適化していきます。 フレームの推論を終えた後、次の フレームを入力し追跡を続けることになります。しかし単に最後の予測値 を次の初期値にすると、見た目の一時的な変化(すなわち特徴量の変化)のせいでトラッカーがずれるという問題や、その時対象が隠れていた場合見失ってしまうという問題があります。そこで、オクルージョンフラグを参照し一番よく見えている時刻 の座標と、初期値の を用いて再追跡を行います。 デモ 以下のデモ動画では馬のかかとを追跡させており、提案手法では足が手前の柱に隠れても見失っていないことがわかります。 https://particle-video-revisited.github.io/videos/horse_all.mp4 particle-video-revisited.github.io 感想 物体のカテゴリによらずピクセルレベルで追跡ができるというのは魅力的に感じました。物体検出タスクなどにおけるアノテーション支援にも使えそうです。 XMem: Long-Term Video Object Segmentation with an Atkinson-Shiffrin Memory Model 概要 この論文は、1968年に提唱されたAtkinson–Shiffrin memory model 22 という感覚記憶、作業記憶、長期記憶の3つからなる記憶モデルに触発された長時間映像用のVOS手法XMemを提案しています。 この手法の特長はGPUメモリの消費が少ない点と長時間の映像にも対応できる点です。次の図は、短時間の映像データセットDAVISと、合計7000フレーム以上の3つのビデオを含む長時間の映像データセットLong-time Video dataset 23 を用いた実験結果です。左のグラフではモデルが使用するGPUメモリとVOS性能の関係、右では入力映像長を変化させたときの性能の変化が示されています。左から、提案モデルは既存モデルよりもメモリ使用量が少ないにも関わらず高い性能であることが分かります。また右から、既存手法は入力映像長が長くなると性能が劣化する一方で、提案手法の性能は劣化するどころかむしろ向上していることが分かります。STM 24 , AOT 25 , STCN 26 のように特徴メモリが急激に増加する既存手法では、メモリに保存する頻度を落として使用せざるをえず長時間入力に対して安定した性能を発揮できていません。 次の図はSTCNとXMemのスケーリング挙動を詳細に示しています。横軸がフレーム数で、縦軸が精度です。フレームが増加するとSTCNは文脈の欠落により性能が低下するが、XMemは十分な文脈を保持し安定していることが分かります。 提案手法 次の図はXMemの全体像を示しています。この手法はAtkinson–Shiffrin memory modelと同様にSensory memory, Working memory, Long-term memoryの3つのメモリストアから構成されています。Sensory memoryは毎フレーム更新され、非常に局所的な時間の特徴を保存します。Working memoryは 枚ごとに情報を蓄積し、事前に設定された最大保持フレームを超えると情報を圧縮した上でLong-term memoryに移行します。Long-term memoryは事前に設定した最大フレーム数(およそ数千フレーム)を超えると特徴を忘れる機能を持ちます。以上のように超短期、短期、長期の3つのメモリストアを活用することで非常に長い映像でも少ないGPUメモリ使用量で高品質な特徴を捉えることができます。 次の図は1フレームにおける処理をより具体的に示したものになります。 Memory reading 全体像で示されているMemory reading操作は上図のQuery, Affinity, Readout featuresの一連の流れに該当し、Working memoryとLong-term memoryから最適な特徴量 を取り出し、Decoderを通すことで推定マスクを生成します。 Working memoryとLong-term memoryで使われているメモリは、既存手法のSTCNと同様のものです。ここにはQuery encoderによってエンコードされた入力画像をkeyとし、Value encoderによってエンコードされた出力マスクをvalueとする辞書が保存されており、新しく入力されエンコードされた画像に最も適合するvalueを次の方法で読み出しています。 まずQuery encoderから出力される Query と、Working/Long-term Memoryに保存されているkey との比較から得られる類似度行列に対してsoftmaxを適用することで、Affinityと呼ばれる行列 を生成します。そしてそれを基にMemory中のvalue を重みづけたもの をMemory readingの出力 とします。 このAffinityを計算するとき、STCNで指摘されているように類似度行列の計算にL2距離を利用するとdot product 27 よりも安定するようです。しかし、次の図の(a)で示しているように全てのメモリ要素を一律に考慮しているためそれぞれの信頼度を符号化できない表現力の乏しさが難点です。そこで提案手法では収縮項 , 選択項 を用いて次の式(2)のように類似度を表現することで図の(c), (d)のように複雑な類似度関係のモデリングを可能としています。 次の節ではWorking memoryとLong-term memoryの更新方法を説明します。 Working memory Working memoryは高解像度の特徴を一時的に保存しています。上述の通り入力画像のエンコード結果であるQuery と予測されたマスクのエンコード結果であるValueの辞書からなり、 フレームごとにQueryとValueの組(以降エントリと呼びます)が追加されます。 メモリ節約のために、エントリ数がある閾値 を超えると、新しめのいくつかのエントリと、ユーザーにアノテーションされたフレームがエンコードされている最初のエントリのみを残してLong-term memoryに引き渡します。 Long-term memory Working-memoryから渡されたエントリたち は、次の図ようにプロトタイプと呼ばれるエントリたち に圧縮されてからLong-term memoryの辞書に保存されます。 まず図中のPrototype selectionにあたる部分で 組のエントリを のグリッドに分割して、その 個の中からこれまでに最も多くアクセスされた 個のパッチをプロトタイプのkey として選択します。この時アクセス数は、これまで計算したAffinityの累積和を取ることで計算しています。 次にPotentiationとして、プロトタイプのkeyに対応するvalueを からうまく重み付けして生成します。具体的にはプロトタイプのkey(図中の星マーク)に類似するグリッドを検索し、特徴量を集約してそのkeyに対応するvalue(金色で囲っている星マーク)を生成します。その結果としてプロトタイプのエントリがLong-term memoryに保存されます。 次の図は、Long-term memoryの処理を可視化したものです。最上段の画像がWorking memoryから渡されたエントリに対応する入力フレームです。黄色の十字がプロトタイプの位置を表しており、それに類似する箇所は赤色で表示されています。また、プロトタイプを示すフレームは赤枠で表示されています。具体的な連結処理では、白鳥のクチバシ(2行目)、植生の一部(3行目)、川岸の一部(4行目)、植生と川岸の境界(5行目)、水面の一部(6行目)という意味のある情報が集約されています。 Sensory memory Working/Long-term memoryから読み出した値をデコードする際に、Sensory memoryと呼ばれる、Working memoryよりもさらに短期間の情報を保持するメモリから時間局所性の高い特徴量をデコーダに加えています。このSensory memoryの更新は以下のように行われます。 次の図はSensory memory の更新を示しています。Decoderからのマルチスケール特徴をダウンサンプリングしてGRUへの入力として連結します。 フレームごとにWorking memoryの更新とともに行われるDeep Updateでは、Working memoryへの入力と別のGRUを使ってメモリを更新します。これにより既にWorking memoryに保存されたであろう冗長な情報を、最小限のオーバーヘッドでSensor memoryから破棄できます。 以上がXMemの処理内容です。Working memoryで短期的な記憶を、Long-term memoryを長期的な記憶を保持し、それらから取り出した特徴をSensory memoryの情報と合わせることで推論の時間的連続性を担保していることがわかると思います。 デモ GitHubページ にデモが載っています。10分もある動画に対してもうまく対象の人物をマスクできていることがわかります。 感想 Atkinson-Shiffrin memory modelの3つの記憶モデルを適切なネットワークに落とし込んで設計している点が興味深かったです。 最後に 本ブログでは、私たちが興味を持ったECCV2022の論文についてご紹介しました。NTT Comでは、今回ご紹介した論文調査、画像や映像、更には音声言語も含めた様々なメディアAI技術の研究開発に今後も積極的に取り組んでいきます。 アカデミックな研究に注力したくさん論文を書きたい 最新の技術をいち早く取り入れ実用化に結び付けたい AIアルゴリズムに加え、AI/MLシステム全体の最適な設計を模索したい という方々に活躍していただけるフィールドがNTT Comには多くあります。新卒採用の情報については こちらのページ をご覧ください。 今後も私たちの取り組みをブログ等で発信していきますので、興味を持ってくださった方は是非今後もご注目ください! Muller, M., Bibi, A., Giancola, S., Alsubaihi, S., Ghanem, B.: Trackingnet: A largescale dataset and benchmark for object tracking in the wild. In: ECCV (2018) 1, 9, 10, 20 ↩ Fan, H., Lin, L., Yang, F., Chu, P., Deng, G., Yu, S., Bai, H., Xu, Y., Liao, C., Ling, H.: LaSOT: A high-quality benchmark for large-scale single object tracking. In: CVPR (2019) 1, 4, 9, 10, 20 ↩ Milan, A., Leal-Taix´e, L., Reid, I., Roth, S., Schindler, K.: MOT16: A benchmark for multi-object tracking. arXiv preprint arXiv:1603.00831 (2016) 1, 4, 10, 11, 20 ↩ Yu, F., Chen, H., Wang, X., Xian, W., Chen, Y., Liu, F., Madhavan, V., Darrell, T.: BDD100K: A diverse driving dataset for heterogeneous multitask learning. In: CVPR (2020) 1, 2, 4, 10, 11, 12, 20 ↩ Pont-Tuset, J., Perazzi, F., Caelles, S., Arbel´aez, P., Sorkine-Hornung, A., Van Gool, L.: The 2017 davis challenge on video object segmentation. arXiv preprint arXiv:1704.00675 (2017) 1, 4, 11, 20 ↩ Voigtlaender, P., Krause, M., Osep, A., Luiten, J., Sekar, B.B.G., Geiger, A., Leibe, B.: MOTS: Multi-object tracking and segmentation. In: CVPR (2019) 2, 4, 12, 13, 20 ↩ Lukezic, A., Matas, J., Kristan, M.: D3S-a discriminative single shot segmentation tracker. In: CVPR (2020) 2, 4, 10, 12 ↩ Voigtlaender, P., Luiten, J., Torr, P.H., Leibe, B.: Siam R-CNN: Visual tracking by re-detection. In: CVPR (2020) 2, 3, 4, 10, 12 ↩ Meinhardt, T., Kirillov, A., Leal-Taixe, L., Feichtenhofer, C.: TrackFormer: Multiobject tracking with transformers. arXiv preprint arXiv:2101.02702 (2021) 2, 3, 4, 11, 13 ↩ Wu, J., Cao, J., Song, L., Wang, Y., Yang, M., Yuan, J.: Track to detect and segment: An online multi-object tracker. In: CVPR (2021) 2, 4, 5, 11, 13 ↩ Wang, Q., Zhang, L., Bertinetto, L., Hu, W., Torr, P.H.S.: Fast online object tracking and segmentation: A unifying approach. In: CVPR (2019) 2, 4, 5, 12 ↩ Lukezic, A., Matas, J., Kristan, M.: D3S-a discriminative single shot segmentation tracker. In: CVPR (2020) 2, 4, 11, 12 ↩ Liu, Z., Lin, Y., Cao, Y., Hu, H., Wei, Y., Zhang, Z., Lin, S., Guo, B.: Swin transformer: Hierarchical vision transformer using shifted windows. In: Proceedings of the IEEE/CVF International Conference on Computer Vision. pp. 10012–10022 (2021) ↩ Zhu, X., Su, W., Lu, L., Li, B., Wang, X., Dai, J.: Deformable detr: Deformable transformers for end-to-end object detection. arXiv preprint arXiv:2010.04159 (2020) ↩ Dosovitskiy, A., Fischer, P., Ilg, E., Hausser, P., Hazirbas, C., Golkov, V., Van Der Smagt, P., Cremers, D., Brox, T.: Flownet: Learning optical flow with convolutional networks. In: Proceedings of the IEEE international conference on computer vision. pp. 2758–2766 (2015) ↩ Zhu, X., Wang, Y., Dai, J., Yuan, L., Wei, Y.: Flow-guided feature aggregation for video object detection. In: Proceedings of the IEEE international conference on computer vision. pp. 408–417 (2017) ↩ Meinhold, R.J., Singpurwalla, N.D.: Understanding the kalman filter. The American Statistician 37(2), 123–127 (1983) ↩ Zeng, F., Dong, B., Wang, T., Zhang, X., Wei, Y.: Motr: End-to-end multipleobject tracking with transformer. arXiv preprint arXiv:2105.03247 (2021) ↩ Xu, Y., Ban, Y., Delorme, G., Gan, C., Rus, D., Alameda-Pineda, X.: Transcenter: Transformers with dense queries for multiple-object tracking. arXiv preprint arXiv:2103.15145 (2021) ↩ Peter Sand and Seth Teller: Particle Video: Long-Range Motion Estimation using Point Trajectories. In: CVPR (2006) ↩ Zachary Teed and Jia Deng: RAFT: Recurrent All-Pairs Field Transforms for Optical Flow. In: ECCV (2020) ↩ Atkinson, R.C., Shiffrin, R.M.: Human memory: A proposed system and its control processes. In: Psychology of learning and motivation, vol. 2, pp. 89–195. Elsevier (1968) ↩ Liang, Y., Li, X., Jafari, N., Chen, J.: Video object segmentation with adaptive feature bank and uncertain-region refinement. In: NeurIPS (2020) ↩ Oh, S.W., Lee, J.Y., Xu, N., Kim, S.J.: Video object segmentation using space-time memory networks. In: ICCV (2019) ↩ Yang, Z., Wei, Y., Yang, Y.: Associating objects with transformers for video object segmentation. In: NeurIPS (2021) ↩ Cheng, H.K., Tai, Y.W., Tang, C.K.: Rethinking space-time networks with improved memory coverage for efficient video object segmentation. In: NeurIPS (2021) ↩ Oh, S.W., Lee, J.Y., Xu, N., Kim, S.J.: Video object segmentation using space-time memory networks. In: ICCV (2019) ↩
アバター
こんにちは。ソリューションサービス部 スマートエンジニアリング部門の加藤( @katomasa23 )です。 新卒2年目で、普段はパブリッククラウド上に構築しているサービス提供基盤の運用自動化に携わっています。 今回私は、普段の業務を飛び出して、ドコモグループのエンジニアが日々の業務から得た学びなどを共有するイベント「dcc Engineer Day 23」 1 の運営に携わりました。 この記事では、イベントの様子や、自ら手を挙げてイベント運営に参加したことで得た学び・知見を紹介します。 dcc Engineer Day 23とは? dcc Engineer Day 23は、NTTドコモ・NTTコミュニケーションズ・NTTコムウェア3社の社員が技術を軸に交流を深める場として開催したイベントです。今年は2023年1月24日に開催し、昨年に続き2回目となります。 今回のビジョンは 「こえてあつまる こえてつながる こえてうみだす」 です。ドコモグループとして「dcc間の壁」や「職種」を こえてあつまり 、 つながり ができ、そして、新たな行動や企画が うみだされる ことを目指しています。 イベント当日の詳細は後ほど詳しく述べますが、反響を簡単に共有します。365名の方が事前登録し、イベント用に作成したSlackチャンネルへの参加は372名、ライブ配信の最大同時接続は210名でした。アンケートでは、「参加できなかった同僚に勧めたいか?(n=77)」という問いに対してNPS 2 50.6と、事務局側としては高い評価をいただくことができたと思っています。 イベント後には、会社をこえて登壇者にcoffee chat 3 を申し込んでみたという声も聞こえてきており、ビジョン達成に少しでも近づけたのではないかと考えています。 当日のコンテンツ 外部からスピーカーを招いた基調講演を2セッション、社員セッションを8セッション実施しました。 基調講演の1セッション目はNTT東日本 特殊局員の登 大遊さんをお迎えし、「世界に普及可能な日本初のサイバー技術の生産手段の確立」をテーマに講演いただきました。これからの日本が自ら新しいクラウドサービスやセキュリティ技術などを生み出す必要性やその方法論について、ご自身の経験などを交えながら紹介いただきました。サーバーラックに神棚が置いてある様子や、配線ばかりで足の踏み場もない居室の写真などを見て、現場の面白さ、技術の奥深さなどを感じました。 基調講演の2セッション目は株式会社クレディセゾン CTOの小野 和俊さんをお迎えし、「CSDX: クレディセゾンのDXへの取り組み」をテーマに講演いただき、その後、NTT Com 技術顧問の及川 卓也さん、イベント事務局の岩瀬さんを交えたパネルディスカッションを行いました。講演では、クレディセゾンのDXへの取り組みの全体像や、デジタル人材の育成手法などについて紹介いただきました。私の印象に残ったのは、バイモーダル戦略の説明です。新卒2年目の私は、バイモーダル戦略は事業計画などマクロな視点での話だと思っていましたが、身近な業務やチーム作りでも参考にできる考え方だと、講演を聞いて思いました。 また、パネルディスカッションでは撤退基準の話が盛り上がりました。講演の中で、「新しい取り組みをする時には撤退基準を決めておき、その基準を下回ったらきちんと撤退することが大事」という話がありました。その話を踏まえ、パネルディスカッションでは、小野さんが自ら始め、ある程度軌道に乗っていたサービスを自らの手で撤退したエピソードが語られました。サービスを撤退するとなると、撤退した人の責任が問われることも多いようですが、そのようなネガティブな印象・評価が付かない、失敗を許容し、チャレンジしやすくなる評価制度への変革や文化の醸成が必要だというコメントが印象に残りました。 社員セッションもすべて詳しくご紹介したいところですが、NTT Comの開発者ブログということもありまして、NTT Com社員のセッションに絞って紹介します。 コミュニケーション&アプリケーションサービス部の宇田川拓郎さんからは「テキスト解析ソリューション開発におけるスクラムの実態」をテーマに発表がありました。COTOHA APIを活用したソリューションのプロトタイプをスクラムで開発する中で、スクラムを機能させるために様々な課題をどう乗り越えたか、というのが主なトピックでした。雑談部屋やSlack上での分報、Coffee Chatの取り組みも紹介があり、私の業務チームはスクラムチームではないものの、チームの関係性向上のためにも導入してみるのは良さそうだと感じました。 ヒューマンリソース部の三村正法さんからは「育成システムの価値を最大化しユーザーに届けるためのHR社員の挑戦」をテーマに発表がありました。社内での循環型の学び合い文化を目指した「みんなの学びシェア」施策を開発する中で、プロダクト組織ではないヒューマンリソース部でもプロダクトマネジメントを活用することができ、実践から得られた学びが主なトピックでした。社内で私もユーザーとして利用することがある施策ですが、それを実例にプロダクトマネジメントについて学ぶことができ、私も日々の仕事に取り入れてみたいと感じました。 私が運営に携わったきっかけ ここからは、私が入社2年目で「dcc Engineer Day 23」の運営に携わった経緯や、運営の立場から得た学び・知見を紹介していきます。 まず、私が運営に携わったきっかけは、全社のエンジニア向けSlackワークスペースでふと見かけた岩瀬さん( @Iwashi86 )のこの投稿でした。 この投稿があったのが8月上旬。 私はちょうど7月に今の部署に異動してきたこともあり、まだ部署の雰囲気に慣れていなかったり、上長との関係構築もまだまだだったりと、応募するにはかなり高いハードルが待ち受けていました。 しかし、以前から度々名前を目にしていた岩瀬さんや、NTT Comだけでなく、ドコモ・コムウェアのつよつよエンジニアの方々と接点を持って、もっと視野を広げたい!面白いことをしたい!という気持ちの方が強く、気がつけば上長に相談DMを送っていました。 配属直後のお願いにも関わらず、上長からはOKの返信をいただき、岩瀬さんにDM。岩瀬さんからも歓迎の返信をいただき、運営事務局にアサインされることになりました。 初回ミーティングから圧倒…いつもと違うメンバーでの仕事を経験してみて 9月ごろから事務局としての活動が始まりました。 事務局の活動は基本オンラインで、週に1回のミーティングがベースです。 初回はチームビルディングということで、miroを用いたアイスブレイク等を実施しました。 普段の業務ではmiroを使ったミーティングをすることがないのと、初対面の方がたくさんいらっしゃるので超緊張です。ましてや、NTT Comから参加の岩瀬さん、水嶋さん( @mizuman_ )とも会ったことがないので何かやらかしたらどうしよ…と思っていました。 しかし、実際に始まってみると、たった1時間でアイスブレイクからイベントビジョンの骨子策定まで終わっていました。 普段の業務で参加している会議とはジャンルが違うので比較にはなりませんが、初対面の人同士で、かつオンラインでこんなスピードで物事が決まっていくのか、とただただ圧倒されました。 何回か週1回の定例を経たのち、10月ごろに細かな役割分担をしました。私は会場のメイン担当をすることになりました。会場担当で特に難しかったのは、どの会社でオンサイト会場を設けるか、という調整です。各社の広めの会場を教えてもらい、全体ミーティングで話し合いをするのですが、なかなか決まらない週が続きました。様々な観点がメンバーから毎週のように出てくるのに加え、自分自身も「2年目なので…」というやや受け身な姿勢が影響していました。 そこで一旦、受け身な姿勢をやめて、ビジョンを反映した「つながり」が会場で生まれやすいかどうか、現地・オンライン参加者の両方のUXが良くなるか、といった考えを念頭に、判断軸(オンライン配信設備の有無など)を立てることを提案しました。事務局の皆さんには受け入れていただき、そこから判断に必要な情報のみに絞って検討した結果、約1か月かけてオンサイト会場がNTT Com会場に決まりました。事務局では各メンバーが自担当に対して自律的に動く必要があったので、もう少し早い段階から積極的に動いてもよかったと反省しました。 年末年始のコロナウイルスの流行の影響を鑑み、オンサイト会場はパブリックビューイング会場としました。登壇者はリモート、あるいはNTT Com社内の会議室から発表してもらい、オンサイト会場は講演を見ながら参加者同士のコミュニケーションを取ってもらう場としました。一時はオンラインのみにするという話も出ましたが、一度ビジョンに立ち返ることで出てきた案であり、「New Normal」への第一歩を踏み出すことができたと思います。 当日、会場面では大きなトラブルもなく、来ていただいた方から「NTT Comの会場、いいですね」「来年もここでやりましょう」といった声をいただきました。ネットワーキングスペースの配置や、様々なレイアウトを試行錯誤するなどしたことで、会場自体の魅力+αの評価をいただけて嬉しかったです。また、今回、普段の業務と違うメンバーとイベントを作り上げることで、普段の仕事の進め方などを見直すきっかけにもなり、非常に収穫の多い経験となりました。 おわりに 今回は新卒2年目が所属部署を超えて、ドコモグループのエンジニアイベントの運営に携わった話を紹介しました。普段はソリューションに関わる業務をしていますが、発表はサービス・プロダクト開発の話が多く、ドコモグループ、ひいてはNTTはサービスの会社だなと改めて感じました。 ただ、サービス・ソリューションの両輪を持っている企業として、次回以降はソリューション側の話題も入って、両者のナレッジが交わるようなイベントにもなったらいいなと思うところです。(所属部署の定例でこのコメントを述べたところ、「来年は登壇者になったらいいじゃない!」と言われたのはここだけの話…笑) 今回の経験で視野が広がったので、日々の業務などを通して、エンジニアとしてパワーアップしていきたいと思います。 また、NTT Com社内では、今回のような本業を超えてナレッジを共有したり、社員同士のコラボを促すイベントを実施したりする雰囲気があり、その様子を少しでもこの記事を通して感じていただければ嬉しいです。 最後にお知らせですが、NTT Comは一緒に働く仲間を募集中です。詳しくは下のバナーから採用情報をご確認いただけると嬉しいです! dccとは、NTTドコモ(docomo)・NTTコミュニケーションズ(Communications)・NTTコムウェア(Comware)の3社の頭文字を取って、3社を表現する言葉です。一般的呼称というよりも、社内で表現する際に使う言葉です。 ↩ NPSとは、Net Promoter Scoreの略で、顧客ロイヤルティを図る指標。(参考: NPS®とは(ネットプロモータースコア)顧客満足度に変わる新指標 - NPSソリューション | NTTコム オンライン ) ↩ coffee chatとは、GitLab社の文化として根付いている、非同期コミュニケーションを円滑にするための雑談。(参考: GitLabで学んだ最高の働き方 Developers Summit 2022-02-18 ) ↩
アバター
はじめに こんにちは、ソリューションサービス部ICTイノベーション部門の安部、江口、谷内です。 私たちのチームでは2022年7月より「脳の健康チェックフリーダイヤル」サービスの機能開発を担当し、世界アルツハイマーデーである同年9月21日に無償トライアルを開始しました。ニュースリリースをはじめ、TVや新聞等の各種メディアで取り上げられたこともあり、現時点で約45万回以上ご利用いただいています。 本記事では開発に至る経緯や、システム構成・開発体制ならびに今後の展望まで、プロジェクトの全体像をご紹介させていただきます。今後の開発のご参考になれば幸いです。 脳の健康チェックフリーダイヤルのご紹介 脳の健康チェックフリーダイヤルは、電話をかけて日付と年齢を発話するだけで、AIが20秒程度で認知症の疑い有無を判定できる無料のサービスです。 サービスの詳細は こちらのURL をご参照ください。 ここから、本サービスの成り立ちなどを少しだけご紹介させていただきます。 プロジェクトのはじまり 「認知症で不安になる本人・家族・企業が少なくなる社会へ」をコンセプトに作ったサービスです。 NTT Com の社内ビジネスコンテストから生まれ、プロジェクト(以下:PJと記述)の中心メンバーの祖父母が認知症になった経験から、認知症で大変な思いをする人が少しでも減るようにという想いが込められています。 サービスコンセプト 高齢の方にも気軽に使っていただけるように、Web上やアプリではなく、電話で利用できることにこだわって制作しました。 脳の健康チェックフリーダイヤルサービスには以下のような特長があります。 事前の登録やダウンロードが不要 自宅の固定電話や携帯電話で利用できる 誰にも知られずに試すことが可能 サービスリリースから多くの方にご利用いただけているのも、手軽に認知機能の変化を確認できる仕組みだからこそだと思っています。 システムの紹介 構成図 脳の健康チェックフリーダイヤルは、電話を受けるクラウドIVRと、そのバックエンドを担うGCPおよび外部APIを中心に構成されています。 ここでは詳細な説明は控えますが、構成図の上側はユーザーが電話をかけてサービスを利用する経路、下側は管理者がサービスの利用状況を確認する経路となります。 認知機能の判定 認知症の疑い有無の判定については、音声から音声特徴量を抽出し、認知機能の変化をチェックする 日本テクトシステムズ社のONSEI と連携することで実現しています。 ユーザーが発話した音声を利用し、ONSEIのAPIと連携して認知機能の判定結果を取得します。この判定結果をIVRに音声発話させる仕組みを作り、ユーザーに認知機能の変化をお伝えしています。 ログデータの保存 ログデータの保存には、今後の開発でデータベースの構造を変更する可能性があることや、部分的なログデータを取得・更新することを考慮し、GCPのFirestoreを使用しています。 また、ログ参照用のWebアプリを独自で開発しており、こちらのフロントエンドフレームワークにはReactとNext.jsを使用しています。これらはWebサーバー機能を持つフレームワークの中でもJavaScriptを利用するため学習コストが低く、高速描画が可能なため、採用しました。 画面のデザインやコンポーネントの部分には、ReactのUIライブラリであるMaterial-UIを使用しています。 また、開発メンバー全員でログ参照用のWebアプリを利用しながらシステムの改善点を話し合う取り組みも行っており、実際に一部の不具合の改修や追加機能の開発に繋げることもできました。 アプリケーションの実行環境について GCPのアプリケーション実行環境としては、Cloud Run, Cloud Functions, Google Kubernetes Engineなど複数の候補がありますが、今回はGoogle Kubernetes Engine(以下:GKEと記述)を採用しています。 今回GKEを選択した大きな理由を、以下にまとめました。 コンテナ死活監視による自己修復機能 24時間利用可能なサービスのため ローリングアップデートが可能 ダウンタイムなしで継続的なアップデートを行いたいため 自動スケールアウトが可能 通常時とメディア紹介時などでユーザの利用量が数十倍単位で変動するため 脳の健康チェックフリーダイヤルは24時間利用可能なサービスとして提供しているため、ダウンタイムを極力発生させないことが最も重要な観点でした。また、マニフェストファイルベースでリソースを管理することで、環境構築が容易になる、環境間での差分がコード上で確認できるなどのメリットも得られました。 Kubernetesの利用は学習コストが高いイメージでしたが、GKE Autopilotを用いてプロジェクト固有のオペレーション部分のみ設定値を追加して運用することで、短期間で本番環境向けの基盤を構築できました。 開発体制の紹介 すでに多くの方々にご利用いただいている脳の健康チェックフリーダイヤルですが、開発チームは2年目社員2名、1年目社員1名、パートナー社員1名のかなり若いメンバーで構成されています。 本章では、このような開発経験の浅いチームが、2カ月という構築期間の中でサービスリリースを達成するまでに直面した課題と、その解決にいたった成功要因を紹介いたします。 若いチームならではの課題 開発経験に乏しいチームの課題として、真っ先に直面するものは、多くは経験の少なさによるものかと思います。本PJは、2022年9月21日の世界アルツハイマーデーにリリースするという、スケジュールが決まったウォーターフォール型のPJとしてスタートしました。しかし、私たちのチームではウォーターフォールでの開発経験がなかったため、開発プロセスの習熟面に課題がありました。また、チームとしてインフラ構築を中心としたシステム開発の知見に乏しく、メンバー間でも知識に差がある、といったスキル面での課題がありました。 一方で、今回のチームならではの強みもありました。それは、チームビルディングの面と、スクラムでの開発経験値です。今回の開発メンバーは、別案件のスクラムチームのメンバーがほぼそのままアサインされる形だったため、すでにお互いのスキルや開発環境の把握、関係性の構築などが完了している状態でした。また、ウォーターフォール開発には疎いものの、スクラムのフレームワークに対する共通認識は持つことができていました。 そのため、私たちは実際の開発に着手する前段階として、今回のウォーターフォール開発PJを、どうにかして自分達の慣れているスクラム”風”に進められないかを考えるところからスタートしました。 開発プロセスの課題解決につながったこと スクラム風に開発を進める ここではスクラムフレームワークに関する説明は省きますが、スクラムは柔軟に変化に適応するための開発手法のため、成果物と納期が明確になっているウォーターフォール型PJには本来適応できません。しかし、本PJのPMがスクラムに理解のある方だったこともあり、開発メンバーからスクラムの様に開発を進めたい旨を伝えると、快く相談に乗っていただけました。結果的に、以下のような取り組みのもと、擬似的にスクラムの様に進めることができました。 WBS(進捗管理表)の中項目をプロダクトバックログ(プロダクトの改善に必要なもの)と見立てた PMが定める各項目の期日について理由を確認しつつ、各スプリント(スクラムにおける開発期間の単位。本PJでは一週間)での対応範囲についてPMとの意識合わせを行なった WBSの進捗の遅れに対するステークホルダーとの調整を、PMに柔軟に行なっていただいた 1つのスクラムチームに集中する スクラム風に進めていくと決定した訳ですが、開発着手直後はそれまで所属していたスクラムチームと兼任する形になってしまいました(PJ立ち上げ時にはよくある話かと思います)。しかし、複数のスクラムチームの兼任は基本的にご法度です。スクラムではスクラムイベントと呼ばれる活動に開発期間の20%以上の時間を費やすため、仮に2つのスクラムチームを兼任すると、実作業時間が80%から少なくとも60%以下に減少します(複数案件による認知負荷の増加を考えると、作業効率も悪くなります)。 開発チームとしてはそういった問題を初期段階から認識していたため、ステークホルダーとの相談により3スプリント目(≒3週目)からメンバー全員が本スクラムチームに専念できました。アジリティ高く兼任問題に対応できた点は、スクラムに対する共通認識を持てている本チームの強みが発揮された場面だと感じています。 スキル面の課題解決につながったこと メンバー間のスキル格差を埋める スクラムにおいては、開発メンバー全員がどんなスプリントバックログ(≒開発タスク)にも着手できる状態が理想です。しかし、開発経験が浅い本チームでは、初めて経験するような作業も多く、特定のメンバーしか着手できないようなタスクになってしまう危険性がありました。 その対策として、本チームでは意識的に情報発信の手段を増やし、その頻度を高めています。具体的には以下の内容に継続して取り組んでいます。 タスク着手時にSlack上で作業状況を実況するスレッドを立てる 後続タスクを進める上で必要になる情報、ノウハウがあればNotion上に開発備忘録としてまとめる スプリント内でマージされたコードに対して、スプリント最終日に全員でコードリーディングを行う 定期的に全員でアプリケーションのリファクタリングを兼ねたコードリーディングを行う 毎週不安のある技術要素などについて勉強会を行う これらの活動は、時間的コストがもちろんかかるのですが、特に経験の浅いチームにおいては、最終的に費用対効果がプラスになると感じています。チームメンバー全員の理解の助けになることも勿論ですが、何かしらのアウトプットを伴うことで、タスクに着手したメンバー本人の理解度も向上し、結果的に後続タスクの実施速度も上がるためです。 実際に達成したベロシティ(タスクの重さの相対的な見積もり値)の推移を見てみると、スプリントが進むにつれ大きくなっていく結果になりました。 これには、スキル共有や自発的なアウトプットにより、PMに管理されなくても開発メンバーが率先して動くことができる、自己管理型のチームとして動けたこともプラスにはたらいていると感じています。 経験値のあるメンバーを最大限活かす 一方で、若手メンバーだけでは解決できない技術課題があったことも事実です。システムのセキュリティ対策や、根が深いエラーの調査など、対応難易度が高いタスクについては、経験を積んでいるパートナー社員の方に集中的に対応していただく機会が多かったです。 これは経験値のあるメンバーに丸投げするという話ではなく、各スプリントのベロシティを最大化するためにどのように行動するのが最適かを、開発メンバーが自律的に判断して出した結果になります。スキルを持つメンバーに技術難度の高い課題にフルコミットしてもらえるように、チームとして柔軟に動くことが出来た点も、短期間でのリリースに繋がったと感じています。 まとめ 私たちのチームでは、開発経験の浅いチームによくあるであろう課題について、以下のように取り組みました。少しでも参考になる取り組みが見つかれば幸いです。 ウォーターフォールでの開発経験がない スクラム”風”開発のためのステークホルダーへのはたらきかけ 1つのスクラムチームに集中するためのはたらきかけ システム開発のスキルに乏しい 開発メンバーの自発的なアウトプットの仕組み作り チームのベロシティを最大化する意識付け 脳の健康チェックフリーダイヤルの今後 脳の健康チェックフリーダイヤルは、少しでも多くの方に、より早く使っていただきたいという思いから、まずは最小限の機能でリリースをしています。 一方で、より「認知症で不安になる本人・家族・企業が少なくなる社会」の実現を目指して、現在も開発を続けております。その内容を一部、ご紹介させていただきます。 サービス機能の拡充 認知症の一層の早期発見・早期治療を促していくため、認知症の一歩手前の状態「軽度認知障害」、もしくは「MCI(Mild Cognitive Impairment)」といわれる状態を検知する仕組みを検討しています。こちらの仕組みを実現し、脳の健康チェックフリーダイヤルへ組み込むことを目指して、日々開発に取り組んでいます。 また、データの収集/分析の機能を組み込み、「データ利活用」ができる仕組みとすることで、より社会貢献につながるサービスを目指しています。 パートナー企業様との協業 このサービス単体で何かを成し遂げるには限界があるため、利用者の悩みを解決できるようなパートナー企業様との協業を、本格的に実施していきます。 とてもありがたいことに、2022年9月21日のニュースリリースの段階で、この取り組みに対して多くのパートナー企業様に共感・賛同いただきました。「認知症で困る本人・家族・企業が少なくなる社会」の実現に向けたさまざまな分野での取り組みを加速するため、認知症に関する社会課題解決にともに取り組んでいただけるパートナー企業様を引き続き募集しております。 おわりに 今回は「脳の健康チェックフリーダイヤル」サービスの開発の裏側をご紹介しました。 今後はサービスをより多くの人に使って頂けるよう、パートナー企業様と連携しながら、日々改善に取り組んでいく所存です。2023年3月末まで無料で利用可能の予定ですので、皆様も是非ご自身やご家族などの健康増進にご活用ください! 最後までお読み頂き、ありがとうございました。
アバター