TECH PLAY

NTTドコモビジネス

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

602

こんにちは。マネージド&セキュリティサービス部セキュリティサービス部門の閏間です。総合リスクマネジメントサービス「 WideAngle 」の新サービスの企画を担当しています。 本記事では、私がセキュリティの知識・技術向上のために業務外で取り組んでいるバグバウンティプログラムについて、3回にわたって紹介します。 本記事により、バグバウンティプログラムの有効性と、脆弱性探しのおもしろさの両方を伝えられれば幸いです。 (前編)バグバウンティプログラムの有効性について (中編)脆弱性探しの魅力と調査方法について【本記事】 (後編)実際に発見した脆弱性の詳細について なお、バグバウンティに関する記事としては、 NTT Com社内バグバウンティのご紹介 もありますので、ぜひそちらもご覧ください。 脆弱性探しはおもしろい 本記事では、バグハンターとしての経験を振り返りつつ、脆弱性探しの魅力をお伝えしたいと思います。 脆弱性探しに取り組み始めたきっかけは、NTT Comの前に勤務していた会社で、セキュリティ機能の開発担当になったことです。知識向上だけでなく、実践を通した技術力向上も目指したいと考えていたところに、サイボウズの脆弱性報奨金制度のことを知り、取り組み始めました。3ヶ月くらい取り組み続けていたところで初めて脆弱性が認定され、達成感が感じられたので、以来ずっと続けています。 ちなみに私は脆弱性診断を業務として実施したことはなく、脆弱性探しはあくまで趣味として取り組んでいます。ただ趣味といっても、楽しいだけの趣味とは異なり、しんどい思いをする場面も多いです。とくに、長期間脆弱性が見つからないときは気持ちが沈みがちです。しかしうまくいけば後述するような達成感や充実感が得られるので今も続けられています。これまでに見つけた脆弱性は8年間で100件ほどです。トップクラスのバグハンターになるとひと月に20件くらいのペースで脆弱性を発見し認定されているので、私はそれほどハイペースではありません。細く長く続けているといった感じです。 脆弱性探しによりさまざまな達成感が得られる 私の場合、以下のような点で達成感や充実感が得られるのがうれしいので、脆弱性探しに取り組んでいます。 力試し 知的好奇心を満たす 本来できないはずのことができてしまうのが楽しい 品質向上への貢献 報奨金を得る バグバウンティプラットフォームが公開するレポートで、バグハンターがバグバウンティプログラムに参加する目的についてのアンケート結果が載っていることがありますが、私の目的もおおむねそれらと同じです。もちろんNTT Com社内バグバウンティプログラムにも参加しました。こちらは入社してまだ間もなかったこともあり、話題作りを狙った面もありました。 脆弱性探しに対する心構えとして、私は脆弱性が見つかればいいなという希望的観測ではなく、脆弱性は必ずあるという確信にもとづいて脆弱性探しに臨んでいます。脆弱性を探しても見つからないときは、脆弱性が存在しないのではなく存在してるけれど自分が見つけられていないだけだ、と考えます。言い換えれば最後は執着心や執念で脆弱性を見つけているのですが、その気持ちを維持できるのは、過去に他社でソフトウェア開発をしていた経験によるものです。開発当初に作りこんでしまった潜在バグが数年後に見つかる、という経験を何度も繰り返した結果、ある程度の規模のソフトウェアのバグを0にするのは極めて困難である、と考えるに至りました。このような考えを持っているため、あきらめずに脆弱性を探し続けることができます。 カジュアルな手法でも脆弱性は見つけられる 脆弱性を探す方法としては、例えばWebアプリケーションを構成するJavaScriptやOSS(Open Source Software)ライブラリのソースコードを読んだり、見つけたあやしい箇所に対して攻撃するためのツールを作って実行したりする方法があります。しかし私はそのような高い技術力を必要とする方法ではなく、WebブラウザーでいろいろなURLにアクセスしてみたり、ローカルプロキシーツールを使ってHTTPリクエストのパラメータを書き換えてみたりするといった、比較的カジュアルな方法で脆弱性探しをすることが多いです。意外に思われるかもしれませんが、そのようなカジュアルな方法でも、結構脆弱性は見つかります。 大まかには、以下のような手順で脆弱性を探しています。 何が資産かを考える。 ユーザーが保存するデータ、設定値、ユーザーアカウント情報、ログデータ、機能を利用できる権利、etc... 資産に対し、情報セキュリティの3要素(機密性、完全性、可用性)のいずれかの点で問題を起こすことを考える。 一般的に、機密性、完全性、可用性のいずれとも関係ない事象は、脆弱性であると認定されないため。 アプリケーションへの入力を、通常とは異なる形に変化させ、処理された結果問題が起きることを目指す。 コンピューターの動作の仕組みは、ものすごく単純化すると、入力→処理→出力、となるが、これをふまえると、おかしな出力を得るにはおかしな入力を与えればよいため。 ひと口にアプリケーションといっても、Webアプリケーションやネイティブアプリケーションなどいろいろなタイプのものがあります。例えばサイボウズOfficeはWebアプリケーションの一例ですし、ネイティブアプリケーションの例としてはGoogle Chromeなどが挙げられます。WebアプリケーションはWebサーバーとの通信がHTTPとして規格化されており、後述するローカルプロキシーツールで簡単に通信に介入できるといった特徴があるため、入力をいじりやすいです。その意味で、Webアプリケーションは脆弱性を探索しやすいタイプのアプリケーションともいえます。 もちろん、アプリケーションがとり得る入力をすべて網羅的に調査するのはあまりに非効率です。そのため典型的な脆弱性をまとめたドキュメントを読んで、脆弱性のパターンを理解したうえで調査するようにしています。例えば以下のドキュメントです。 安全なウェブサイトの作り方 (IPAが制作、公開) Web Hacking 101 (Peter Yaworski著) 「Web Hacking 101」はHackerOneにユーザー登録すると無償でPDF版をもらえます(少なくとも私が登録した時はもらえました)。「Web Hacking 101」には実際に発見された脆弱性の実例について詳細が書かれており、SSRF(Server Side Request Forgery)やXXE(XML External Entity)といった、比較的新しい種類の脆弱性についての記載もあります。脆弱性の実例に関する情報は Hacktivity (HackerOneのバグハンターの活動記録)などでも得ることができますが、ここで挙げたようなドキュメントで全体像を把握することもよいことだと思います。 典型的な脆弱性を探す調査のほか、セキュリティ機能の基本であるAAAも定番の調査対象です。AAAとはAuthentication(認証)、Authorization(認可)、Accounting(アカウンティング)の3つの機能のことを指します。もしこれらのセキュリティ機能が正しく動作していなければ、直接的な機密性/完全性/可用性の問題がなくとも、それだけで脆弱性といえる場合が多いからです。例えば、パスワード変更してもログインセッションが有効な状態で残っていたり、本来アクセス権のないデータを参照できたり、監査ログの改ざんができたりするといった類のものです。 そのほかには機能が複雑になればその分脆弱性が入りこむ可能性が高まると考え、機能的に複雑な部分を狙って調査することもあります。また、長くバグバウンティプログラムが実施されているソフトウェアは新たな脆弱性が見つかりにくくなる傾向があります(これを「枯れた」状態と呼びます)。そのようなケースでは、アップデート時に追加された機能に絞って調査する、という方法も有効です。 ローカルプロキシーツールを使用することは超基本 調査にあたっては、ローカルプロキシーツールと呼ばれる、WebブラウザーとWebサーバーの間のデータのやりとりを仲介するツールをよく使います。このツールを使うと、WebブラウザーとWebサーバーの間の通信の中身を見たり、Webサーバーに送信するデータを書き換えたりすることが可能です(実際にはほかにももっと豊富な機能が備わっています)。また、よく使われているローカルプロキシーツールの多くはHTTPS通信にも対応しています。「Webブラウザー - ローカルプロキシーツール」、「ローカルプロキシーツール - Webサーバー」という2つのセッションに分離し、仲介するローカルプロキシーツール上で一度HTTPSを復号することで、データの参照・書き換えを実現しています。 代表的なローカルプロキシーツールとしては以下のものがあります。 Fiddler OWASP ZAP Burp Suite Community Edition Fiddlerは、起動しただけでWindowsのプロキシー設定をFiddlerの待ち受けポートに自動的に変更しますが、この点が便利なのでお気に入りでした。しかし後継のFiddler Everywhereは旧版(Fiddler Classic)の全機能を引き継いでおらず、脆弱性探しには欠かせない「HTTPリクエスト送出をインターセプトしてパラメータを書き換える機能」すら備えていないので、最近は別のツールに浮気中です。もともとFiddlerは脆弱性調査を目的としたツールではなく、Webアプリケーションのデバッグが目的なので仕方ないのかもしれません。 また、最近調査したWebアプリケーションではWebSocketを使用していたのですが、FiddlerだとWebSocketのメッセージ書き換えができないことも、Fiddlerから気持ちが離れ気味なもう1つの理由です。OWASP ZAPであればWebSocketのメッセージ書き換えに対応しているため、最近はもっぱらOWASP ZAPを使うことが多いです。私は試したことはないのですが、Burp SuiteでもWebSocketのメッセージ書き換えができるようです。 脆弱性探しに興味を持たれた方は、まずはOWASP ZAPかBurp Suiteをインストールして、WebブラウザーとWebサーバーの間でどのような通信が行われているかを眺めるところから始めてみるとよいと思います。 なお、「ローカルプロキシー」という呼称ですが、Webブラウザーと同じPCで動作させるという意味で「ローカル」という言葉が使われているのだと思われます。しかしリモート(Webブラウザーとは別のPC)でローカルプロキシーツールを動作させることももちろん可能です。例えばこのことを利用して、スマホアプリの調査をするときは、「スマホ上のWebブラウザー - PC上のローカルプロキシーツール - Webサーバー」という構成で調査できます。 まとめ 本記事では、脆弱性探しの魅力と調査方法についてご紹介しました。 本記事をきっかけに、脆弱性探しに興味を持たれる方がいればうれしいです。 次回 は、私が過去に発見した脆弱性について、技術的な詳細も含めてご紹介します。
アバター
こんにちは。マネージド&セキュリティサービス部セキュリティサービス部門の閏間です。総合リスクマネジメントサービス「 WideAngle 」の新サービスの企画を担当しています。 本記事では、私がセキュリティの知識・技術向上のために業務外で取り組んでいるバグバウンティプログラムについて、3回にわたって紹介します。 本記事により、バグバウンティプログラムの有効性と、脆弱性探しのおもしろさの両方を伝えられれば幸いです。 (前編)バグバウンティプログラムの有効性について【本記事】 (中編)脆弱性探しの魅力と調査方法について (後編)実際に発見した脆弱性の詳細について なお、バグバウンティに関する記事としては、 NTT Com社内バグバウンティのご紹介 もありますので、ぜひそちらもご覧ください。 外部人材を巧みに活用することでシステムの安全性を高められる バグバウンティプログラムとは日本語で脆弱性報奨金制度のことで、企業が自社のソフトウェアやシステムの脆弱性を見つけてくれた人に報奨金を支払う制度のことです。脆弱性探しをする人はバグハンターと呼ばれます。文脈によってはリサーチャー、エシカルハッカー、ホワイトハットハッカーなどと呼ばれることもあります。 バグバウンティプログラム実施の流れとしては、 企業が調査対象のWebアプリケーションなどについて、調査対象URLや禁止事項、報奨金額の目安、詳細な参加規約などを公開する。 規約に同意したバグハンターが調査を行い、脆弱性を見つけたら企業に報告する。 企業が報告内容を評価し、脆弱性であると認定したらバグハンターに報奨金を支払う。 というものが一般的です。 このような流れにより、世界中の多くのバグハンターにより脆弱性を見つけてもらえるチャンスが生まれるわけです。バグハンターは報奨金を得るために脆弱性を探し、企業は見つかった脆弱性に報奨金を支払うとともに脆弱性を修正する、というサイクルを通し、ソフトウェアやシステムの安全性が高まっていくことになります。 もちろん、バグバウンティプログラムという制度とは別に、世の中のセキュリティ向上などの目的のために無報酬で脆弱性探しをされている方もいます。しかし調査規模や発見される脆弱性の件数を大きくするには、報奨金というインセンティブを制度に組み込んで、大規模にバグハンターを集めるという仕掛けが効果的であろうと思います。 ちなみに、「バグバウンティ」といってもどのようなバグに対しても報奨金が支払われるわけではなく、基本的には脆弱性、つまりセキュリティに関する欠陥のみが支払いの対象となります。 バグバウンティプログラムには、従来のテスト手法にない長所がある これまでもソフトウェアやシステムのセキュリティ強化のために、セキュア開発ライフサイクルの活動により脆弱性の作りこみを防ぎ、脆弱性診断やペネトレーションテストを通して運用フェーズへの脆弱性の流出を防ぐ、といった取り組みが行われてきました。 この上さらにバグバウンティプログラムを実施することにどのような意味があるのでしょうか? その答えは、バグバウンティプログラムの特長にあります。バグバウンティプログラムの特長としては以下のようなものがあるといわれています。 多様なバックグラウンドを持つバグハンターによる調査により、ほかのテスト手法で発見できなかった脆弱性が見つかる可能性が高まる。 期間を区切らずに実施しやすいため、世の中のセキュリティ動向に応じたタイムリーな調査につながる可能性が高まる。 (期間を区切らずに実施しやすいことの背景には、報奨金支払いが発生するのは基本的には脆弱性が見つかったときのみで実施期間の長さには依存しないため、企業側から見た経済性の面で有利であるということが挙げられます) 上記のような特長により未知の脆弱性を発見できる可能性が高まるため、バグバウンティプログラムは実施する意義があります。ただし、バグバウンティプログラムはこれまでのテスト手法にとってかわるものではありません。あくまで、これまでのテスト手法を補完する位置づけのものです。 バグバウンティプラットフォームを活用することで、バグバウンティプログラム運営の負荷低減が可能 このようにセキュリティテストとして魅力のあるバグバウンティプログラムですが、企業がすべてを自社でまかなうのは大変です。制度の維持には、以下のように多くの業務や仕組みが必要だからです。 調査ルールや参加規約の策定 バグバウンティプログラムの告知 報告の受け付け窓口の設置 報告内容の検証 報奨金の支払い事務 そこで、企業とバグハンターを結びつけ、制度運営の大部分を代行する、バグバウンティプラットフォームと呼ばれるサービスも誕生しています。以下が主なものです。 HackerOne Bugcrowd Intigriti YES WE HACK BugBounty.jp (私が調べた範囲では、ほかに10数件のバグバウンティプラットフォームがあります) これらのバグバウンティプラットフォームの中には、バグバウンティプログラム運営を支援するのみならず、脆弱性開示ポリシー策定支援、ペネトレーションテストの提供、バグハンター向けトレーニングコンテンツ提供、セキュリティカンファレンス開催、といったさまざまなサービス提供を行うプラットフォームもあります。各バグバウンティプラットフォームは、バグバウンティプログラム数や登録バグハンター数を競うだけでなく、提供するサービスの違いによっても特色を出そうとしているように見受けられます。 現在はバグバウンティプログラムを自社運営するのではなく、上記のようなバグバウンティプラットフォームを利用することが一般的になっています。バグバウンティプラットフォームを活用してバグバウンティプログラムを実施することは、企業が不特定多数のバグハンターに脆弱性探しを依頼するクラウドソーシングである、という見方をすることもできます。 バグハンターから見ても、バグバウンティプログラムを個別に探す必要がないことや、脆弱性報告を統一されたインターフェースで行うことができるといったメリットがあるので、バグバウンティプラットフォームを利用する価値は高いです。CTF(Capture the Flag)を解いて一定のポイントをためたバグハンターをプライベート形式のプログラムに招待したり、バグハンターの脆弱性発見のランキングを公開したりするなど、バグバウンティプラットフォームはバグハンターのモチベーション維持にも色々と工夫しています。 バグバウンティプログラムは、拡大を続けている 本記事のタイトルでは「新たなセキュリティテスト手法」と書きましたが、実際には歴史はそれなりにあります。現在のようなバグバウンティプログラムが初めて行われたのは、1995年のネットスケープコミュニケーションズによるものです( Wikipedia より)。本記事では、日本ではバグバウンティプログラムはまだあまり広まっていないという現状を鑑みて、「新たな」という表現を使いました。 現在バグバウンティプログラムの実施は、バグバウンティプラットフォームを利用するのが主流ですが、その中でも最大の規模を誇るバグバウンティプラットフォームはHackerOneです。HackerOneは2012年に立ち上がったバグバウンティプラットフォームで、2021年の報奨金総額は3692万5156ドルにものぼります( Hacker-Powered Security Report: Industry Insights '21 より)。バグバウンティプログラム数は、パブリック形式(公開されているもの)だけで約380件あります。2022年1月に4900万ドル(シリーズEラウンド)の資金調達に成功しており、会社規模としても拡大を続けているようですが、それだけバグバウンティ事業が活況ということなのでしょう。 日本では、バグバウンティプログラムを自社運営する企業としては、 サイボウズ (2014年~)や Sky (2022年~)があります。HackerOne上でバグバウンティプログラムを実施している日本企業も5社ほどあります。また、日本のバグバウンティプラットフォームであるBugBounty.jpでバグバウンティプログラムを実施している企業も数社あります。さらに2021年9月には、スリーシェイクが Bugty というバグバウンティ運用代行サービスをリリースしています(バグバウンティプラットフォームとしてIntigritiを利用)。 このように、日本ではバグバウンティプログラムを実施する企業の数はまだあまり多くないですが、近年少しずつ増えてきている印象を受けます。今後も増加傾向は続くのではないかと予想しています。 社内限定のバグバウンティプログラムも有効 バグバウンティプログラムの実施を検討しようとしたとき、いきなり社外のバグハンターに調査してもらうことに不安を感じる方もいらっしゃると思います。そういったケースへの1つの解として、プライベート形式でのバグバウンティプログラムというものもあります。不特定多数のバグハンターにバグバウンティプログラムを公開するのではなく、バウバウンティプラットフォーム上で一部のバグハンターのみにバグバウンティプログラムを公開し、調査してもらうという形式です。 別の方法として、社内限定でバグバウンティプログラムを実施するという方法もあります。自社の社員のみにバグバウンティプログラムを公開し、調査してもらうという方式です。この場合は、バグハンターは社員ということで、身元が完全に把握できますので安心感はより高まります。その一方で、多様なバックグラウンドを持つバグハンターに調査してもらえるというメリットは限定的になりますし、運営をすべて自社で行わなければならない(バグバウンティプラットフォームは利用できない)というデメリットもあります。バグバウンティプログラムを実施したい企業の事情に合った方式を選択するとよいのではないかと思います。 冒頭でも紹介した通り、NTT Comは社内バグバウンティプログラムを実施しています。他社で社内バグバウンティを実施している例はあまり聞いたことはなく(大学で千葉大学、電気通信大学があるくらい)、バグハンターの一人として先進的で素晴らしい取り組みだと感じています。今後も制度を継続・拡大させて、お客さまにより安全なシステムをお届けできる体制がより強固になるとよいなと思います。 まとめ 本記事では、バグバウンティプログラムの有効性についてご紹介しました。 システムやソフトウェアを提供する立場の方にとって、本記事がバグバウンティプログラムを検討する1つのきっかけになれば幸いです。 次回 は、私の脆弱性探しの経験をもとに、脆弱性探しの魅力と調査方法についてご紹介します。
アバター
はじめに こんにちは、2月14日から2月25日までNTTコミュニケーションズの職場体験型インターンシップに参加させていただきました関根です。インターンシップにはテレプレゼンスエンジニアとして参加し、VR酔いを軽減するテーマに取り組みました。この記事では、私が体験したインターンシップの内容について紹介できればと思います。 テレプレゼンスロボットとは テレプレゼンスロボットとは、遠隔操作技術や映像転送技術などを組み合わせることにより、遠隔地であっても、まるでその場にいるように活動できるロボットです。 参考: テレプレゼンスのイメージがわかる動画 今回扱ったテレプレゼンスロボットは、HMD(ヘッドマウントディスプレイ)を使用してカメラの向きの操作と、HMDへのリアルタイム映像表示ができるロボットです。3つのサーボモータによりカメラの姿勢をHMDの姿勢と同期させることができ、2つのカメラの映像をネットワーク経由でHMDの右目と左目それぞれの画面に表示することで、ロボット視点の映像を立体的に表示できます。この技術を使うことで、遠く離れた場所で点検作業やケーブルの差し替えのような簡単な作業をしたいときに、現地に行かなくても自分でロボットを操縦して対応できるようになります。 www.youtube.com 課題 一般的なVRゲームでは、HMDのジャイロセンサ等をもとに自己位置推定を行い、仮想空間上のカメラの位置・姿勢を更新しレンダリングすることで、ユーザーの動きに応じた映像変化を実現しています。 対してテレプレゼンスロボットでは、遠隔地のロボットから映像を得る必要があります。 まず、HMDで得られたユーザーの動きを、ネットワーク経由でロボットの姿勢データとして送信します。 次に、この姿勢データに合わせ、カメラの位置と姿勢を内蔵のサーボモータで変更します。 その後、得られたカメラ映像をエンコードし、ネットワーク経由でHMDに送信することでようやくHMD内に映像が表示されます。 このため、テレプレゼンスロボットでは、VRゲームと比べて大きな遅延が発生します。 通信遅延については経路や通信環境に依存しますが、例えば日本にいるオペレータがアメリカにあるロボットを操縦する場合、遅延時間は往復で200msほどになります。エンコードについては使用するハードウェアにもよりますが30〜70msほどになります。また、通信やエンコードの遅延時間だけでなく、ロボットがHMDと同じ姿勢になるまでのモータの駆動時間も遅延に含まれます。なお、今回用いたハードウェア構成では、国内ということもあり、合計すると約120msほどの遅延となりました。 カメラの映像は、HMDの左右の液晶にそのまま表示しています。そのため、HMDを被って左右に頭を動かすと、遅延時間の分だけ向いている方向と映像がズレることになります。この遅延は、HMDの動きに対する映像の遅延であるため、実際にHMDを被らないと認識が難しいです。そこで、分かりやすく可視化するために、シミュレータを作成し動画を作成しました。 www.youtube.com この動画では、遅延がある場合と遅延がない場合を比較しています。 HMDを瞬間的に動かすと、200msの間は画面が固定されたままになります。 逆に200ms後には、何もしていないのに勝手に映像が動くことになります。 これにより、VR酔いと呼ばれる乗り物酔いに近い現象が発生してしまいます。 一度VR酔いが発生すると、テレプレゼンスロボットの使用継続が困難となるため、この課題を解決することは重要です。 応答性の高い高価なモータを用いて抑えることも考えられますが、 今後テレプレゼンスロボットを多用途に普及させる上では、なるべく安価なハードウェアで長距離でも使えるように実装したいところです。 一般的に200msを超えるとVR酔いに耐えられなくなるため、日米間の通信遅延は200msであることを考えると、 遅延時間を減らすアプローチには限界があります。 そこで本インターンシップでは、遅延を許容しつつ映像のズレを軽減できるアプローチを模索しました。 検討手法 テレプレゼンスの場合でも、一般的なVRゲームと同様に、操縦者側のPC内だけで映像の描画ができれば酔いが軽減できると考えられます。 VR酔いの原因は操縦者の姿勢と描画される映像の位置のズレに酔いの原因があるため、 操縦者側のPC内に存在する情報を加工し、この差分を埋めることにしました。 今回は、既に受信済みの映像をリアルタイムに補正することで、遅延がない場合の映像に近づけることにしました。 ロボットからの映像が届いていない状態でも、操縦者の姿勢情報に合わせて映像の描画位置を補正してあげることで、頭部方向と映像のズレを緩和できます。 ただし、カメラの位置のズレに映像の補正で対応するためには、物体との距離が必要となります(カメラを一定量動かした場合、物体が近いほど大きく動きます)。 対して、姿勢のズレに映像の補正で対応する場合、物体との距離は関係ないので、カメラ映像を射影変換するだけで対応可能です。 そこで、今回のインターンシップでは、第一段階として姿勢のズレを低減する方法を提案して設計・実装しました。この射影変換を使った姿勢のズレ補正を可視化した動画が次の通りになります。 www.youtube.com この動画では、遅延している映像を射影変換によって、遅延がない映像に変換できることを示しています。カメラの視野角を超える範囲は表示できませんが、見えている範囲に関しては遅延がない映像と全く同じになります。この射影変換の処理はクライアントPC(HMDのつながっているPC)内で完結するので、通信遅延やエンコードの遅延の影響を受けることはありません。よって、仮に通信遅延が200msあったとしても、姿勢に関して遅延のない映像にできます。 1.カメラの姿勢の取得 この手法で姿勢のズレを無くすためには、HMDから得られる目標の姿勢と、映像の各フレームで撮影されたタイムスタンプにおけるカメラの姿勢を取得し、姿勢のズレだけ映像を回転する必要があります。単純化すると、HMDの角度がαでカメラの角度がβの場合は、α-βだけ画像を回転すれば良い、ということになります。 カメラの角度を取得するためには、ジャイロセンサ等を用いて姿勢推定を行う必要があります。また、得られた姿勢データは、映像の各フレームでのタイムスタンプと同期して、ロボットからクライアントPCに送信する必要があります。最後に、クライアントPCではHMDの姿勢とカメラの姿勢の差分をもとに映像を射影変換することになります。そのため、今回は新しくハードウェア・ソフトウェアの設計・実装をネットワークも含めて行うことになりました。 2.ロボットの姿勢の取得と修正 この手法を実装するにあたり新しく出てきた課題として、ロボットに加わる外力への対応があります。従来の実装では、外力によってロボット本体が回転した場合、HMDが止まっていても映像が勝手に動いていました。しかし、今回の手法では映像が動かない代わりに、映像の表示領域が動くようになります。この現象をシミュレーションした動画が次の通りです。 www.youtube.com 動画では、HMDを動かさずにロボットを外力によって回転させています。従来の補正がない場合では勝手に映像が動きますが、今回の補正では映像自体が動かない代わりに映像の表示枠が動きます。これにより、ロボットを外力で回転したままにすると、射影変換によって動かされた映像の枠が中心からズレて戻らなくなります。テレプレゼンスロボットは、車輪などでロボットごと移動するので、こういった外力への対処は避けて通れません。そこで、カメラの姿勢を一定に保つ姿勢制御(スタビライザのようなもの)を実装することにしました。 www.youtube.com この姿勢制御では、カメラとは独立して、ロボット台座の姿勢推定を行い、ロボットが外力で動かされた分だけ映像を回転させています。 これにより、外力がある状況でも、HMDの姿勢とロボットの姿勢を同期させられます。 (従来は、ロボット台座に対するカメラ姿勢とHMD姿勢が同期していたが、今回は地面に対するカメラ姿勢とHMD姿勢が同期している)。 テレプレゼンスロボットでは、基本的には室内での利用を前提としていることが多いですが、室内でもコンセントボックスやケーブルをロボットが踏むことで揺れてしまうケースは多いです。 特に、ロール軸(視線の方向)で回転させられた場合、地面が傾くような感覚となり、平衡感覚を失ってVR酔いしやすくなります。こういったVR酔いについては、姿勢制御で根本的に改善できます。次の動画は、頻繁に揺らされる場合のシミュレーションですが、姿勢制御があると映像は全く動くことはなく非常に安定しています。 www.youtube.com 実装 今回のインターンシップでは、2週間で部品の選定からコーディングまで実装する必要があったため、なるべく短期間で高い性能を出せる実装方法を模索しました。ハードウェアなので調達に時間がかかり、破損のリスクも高いため、保険となる代替プランも立てつつ、なるべく予定通りに進められるよう心がけました。 1.姿勢推定 姿勢推定は様々なアプローチがあり、突き詰めれば良い性能が期待できますが、短期間のインターンシップのため、姿勢データが直接得られるジャイロセンサBWT901CLを選定しました。BWT901CLは、カルマンフィルタが実装されている9軸センサであり、クォータニオンの姿勢データを200Hzで取得できます。また、バッテリーが内蔵されている他、BluetoothのSPP(Serial Port Profile)によりPCからキャリブレーションや取得データの設定が可能です。 ジャイロセンサの取り付けについては、ロボットにある程度しっかりと固定するため、新しくロボットのカメラとジャイロセンサを取り付けるための治具を設計して3Dプリントしました。テレプレゼンスPJのオフィスに3Dプリンタがあったため、設計データを自宅から送り、社員の方に印刷していただいて、うまく取り付けられるかチェックしてを繰り返しての実装となりました。 2.ネットワーク通信 最終的に SkyWay WebRTC Gateway を用いてインターネット上で転送することを考え、 得られた姿勢データは、UDP通信によりロボットからクライアントPCへ送信できるようにしました。 本来であればこの姿勢データを映像と完全に同期させるため、タイムスタンプを用いた実装にする必要がありますが、 今回は試験的な実装ということで、遅延時間(≒エンコード時間)をキャリブレーションして手動入力することにしました。 3.射影変換 もともとHMDへの表示にはUnityが使われているので、単にUnity上でカメラに対して映像が投影された平面を回転させるだけで実装できます。 ただし、厳密に視野角やUnity上の映像の大きさを現実に合わせる必要があったりと細かい調整が求められました。 また、ジャイロセンサも含めて基本的にロボット制御は右手系ですが、Unityは左手系なので、ロボットとクライアントPC間の通信において相互に座標変換の必要がありました。 4.ロボット制御 当初は、3軸の各サーボモータの角度を取得し、カメラに取り付けたジャイロセンサの姿勢と組み合わせることで、ロボットの台座の姿勢を計算しようとしていました。 しかし実装してみると、サーボモータのAPI取得に27ms必要だと分かったため、60FPS出せない事が分かりました。 そこで急遽予定を変更し、ロボットの台座にカメラ側とは別でジャイロセンサを取り付けました。 そして、ジャイロセンサから得られたロボットの台座の姿勢と、ネットワーク経由で得られたHMDの目標姿勢から、サーボモータの目標となる角度計算の処理を実装しました。 最終的にセンサを取り付けて組み上がった、ロボットの写真は次の通りになります。 成果物 実装後に、HMD画面も含めて実機の映像を撮影し動画にしたものが次の通りになります。 www.youtube.com 動画の前半では、本体を揺らさずにHMDを様々な角度に動かし、射影変換による補正が効いていることを確認しています。 動画の後半では、社員の方にロボット本体を様々な形で揺らしてもらい、姿勢制御が機能していること、姿勢制御下でもHMDの姿勢変更を反映できることを確認しています。 ロボット本体を揺らされた場合にHMDの画面はほぼ動かなかったため、オペレータ視点だとどのように揺らされているか分からない程にしっかりと補正できていたことは非常に良かったと感じました。 また射影変換による補正については、今回は通信遅延が120msとそこまで大きくなく、さらにチューニングが不完全なため、効果が実感しにくかったです。 但し補正自体は想定通り正しく機能していました。チューニングをしっかりし、遅延によるズレの影響を小さくすれば、VR酔いの解決に大きく近づけられると感じられました。 www.youtube.com インターンシップを終えた感想 本当にあっという間で非常に密度の高い2週間でした。 今回の実装により、提案した方法を実際に形にして、映像補正によるVR酔い低減の実装例を示すことができたと感じています。 しかしやり残したことも多く、例えば位置に対する補正であったり、映像と姿勢の同期であったり、まだまだやらないといけない事が沢山あるなと思いました。 通信周りは初めての事が多かったですが、テレプレゼンス以外にも幅広く使えそうな知識が身についたので、色々使っていきたいと思います。 また今回はリモートでのロボット開発となったため、ハードウェア周りで難しい局面も多くありましたが、 社員の方がDiscord経由で常にサポートしていただける体制となっていたため、安心して取り組むことができました。 今回のインターンシップで得られた経験を今後の活動に様々な形で活かしていくことができればと思います。 NTTコミュニケーションズのテレプレゼンスPJの皆様、ヒューマンリソース部の皆様、本当にありがとうございました!  メンターからのコメント メンターを担当したイノベーションセンターの中蔵です。2週間のインターンシップお疲れ様でした。 今回取り組んでもらったVR酔いの緩和は、単にテレプレゼンスロボットを動かす際に直面する課題というだけではなく、 多くの領域の研究者が長年取り組んでいる大きなテーマです。 短いインターン期間でできることにも限りがあるため、 当初はもっと簡単なゴールラインを設定していました。 しかし、折角やるなら本質的な解決に取り組みたいということで、自ら取組内容を企画・提案してくれました。 初めて触るセンサの挙動を把握した上で、ロボットの適切な位置に取り付けて姿勢情報を取得し、 Unity内の空間座標と照らし合わせながら描画位置を変更するという盛り沢山な内容です。 これを期間内に完成させるだけでなく、デモムービーの撮影まで完了させたのは流石の一言です。 この内容を完成させられたのは、高い技術力を発揮しただけではなく、チーム内の先輩社員とうまく連携できたのも大きいと思います。 要件の聞き取りや既成のロボットの構造把握のために受動的なコミュニケーションをとるだけではなく、 必要に応じて先輩社員に作業を依頼するなど、積極的に周囲を巻き込むことができていました。 今後何をする場合でも活かせる能力なので、ぜひこのまま伸ばしていってほしいと思います。 今回のインターンシップで得た経験が、今後の関根さんの研究や取り組みに役立てば幸いです。 改めて、インターンシップへの参加とご活躍ありがとうございました!
アバター
サマリ この記事は3社協同プロジェクトの紹介記事であり、ブログリレーの中編です configを頼りにL1トポロジをNetBox上で再現し、Batfishで解析できるようにしました 障害耐性を測るためにリンク障害を模擬したL1トポロジを自動生成しました Batfishの解析結果からL1/L2/L3の情報をモデルとして抽出し再利用可能にしました はじめに イノベーションセンターの田島です。主にサービスプロバイダ網の技術検証から検証用ASの設計・構築・運用まで担当しています。 この記事は沖縄オープンラボラトリ 1 で実施している、TIS株式会社/ビッグローブ株式会社/NTTコミュニケーションズ株式会社の3社協同プロジェクト「Model Driven Network DevOps PJ」 2 の内容を紹介します。詳細は後述しますが、このPJではトポロジをはじめとするNW構成情報を中心にした設計・構築・運用プロセスの検討とPoCの実施・評価を実施しています。 この記事では下記の図の強調部分を紹介します。前後の部分を分割し全体を3パートに分けて各社持ち回りで紹介するブログリレー形式をとっています。これに加えて前編後編もあわせてご覧いただくと全貌がより一層理解しやすくなります。 ブログリレーの前編:モデルベースなネットワーク自動化への挑戦/ビッグローブ株式会社 ブログリレーの中編:実ネットワークをモデルとして抽象化しオペレーションを高度化するチャレンジ(この記事) ブログリレーの後編:ネットワークのモデルベース検査と障害シミュレーションによる運用高度化への挑戦/TIS株式会社 プロジェクトの概要 我々のプロジェクトでは、ネットワークの複雑化に起因して発生する、人間では事前にネットワークの状態を把握しきれない問題に対処しようとしています。ネットワークの状態を把握できなくなると次のような影響が出ます。 ネットワークの運用、特にオンプレでは構築・変更に対する事前検証およびレビューが重要視される しかし規模の問題などで検証環境では不十分に模擬できない等の状況が発生し、人間の思考力に依存する箇所が発生する その結果レビューの品質が安定しないばかりか、実際にやってみて問題に直面するといった状況が発生しかねない これに対処するため、ネットワークを含めたシステム全体のあるべき姿としてモデルを定義し、モデルデータで各種の検証やテストを行えるソフトウェア群の実装を目指しています。計算機上にモデルを構築し実機との間で同期を取ることで様々なオペレーションの模擬が人間より高い精度で、広い網羅性で、短時間でできるようになると考えています。また、このモデルデータを使うことで1つの巨大なシステムでは無く各コンポーネントの責務が明確になった、対象の環境に合わせた拡張がしやすいソフトウェアを作れると考えています。図のような本番環境とモデルを相互に同期する仕組みを作り、下記のような機能の実現を目指しています。 設計ポリシのルールに各モデルデータが準拠しているのかのチェック シミュレーションによる設定変更やテスト実行 時系列のモデルデータの差分による構成や状態変化の可視化 モデルデータから検証環境の自動構築や機能検証実施 検証済みモデルデータの自動デプロイ 現段階ではネットワーク機器のconfigからモデルデータを構築し、障害試験を含む各種の疎通試験ができています。図中だとターゲットとなる本番環境からモデルを構築し、自動テストを行う部分のイメージです。モデル上だと全パターンの疎通試験を行うことができるので、単一障害点の存在を確実に検出できます。 モデルデータを用いた疎通試験に必要なコンポーネント ネットワーク機器から情報を取得し、モデルを作成し、そのモデルを用いて各種試験を模擬するためには大きく分けて下記の3ステップが必要です。 NW装置のコンフィグを自動定期取得するステップ configからL1/L2/L3に分けてモデルデータを作成するステップ ※この記事の範囲 モデルデータを評価するステップ この記事では2の部分を紹介します。1と3についてはそれぞれビッグローブ株式会社、TIS株式会社の記事をご確認ください。 モデルデータの作成 パート1であるビッグローブ株式会社の記事 によってconfigやstatusを取得できました。それらのconfigからL1/L2/L3の情報を復元していきます。方針としてはBatfish 3 でconfigからL2/L3の再構築ができるため、それ以外の部分を独自実装します。Batfish単体ではカバーできないのは次のような部分です。 2022/03現在、BatfishはIPv6に未対応 → IPv6関連を別途パースできるように開発中 (この記事では省略) L1トポロジは復元されない configから復元する リンク障害模擬・ノード障害模擬などの実験データの機械的な作成 L1トポロジをベースに別途生成する 再利用可能な形式での各レイヤーデータの保存 L1トポロジの復元 configを基にL1の接続状態を調べ、トポロジデータとして利用可能にしました。入力はconfigのテキスト群、出力はBatfish読み込み可能なlayer1_topology.json 4 です。中間生成物として、L1トポロジをデバイスとケーブルで表現したNetBoxインスタンスと、inet-henge 5 で表示可能なトポロジデータが作成されます。トポロジはグラフDBに入れて表現することもできますが、モデルデータの1つとして後々の変更チェックなどで活用できるように、専用UIが付いているNetBoxを使用しました。 レポジトリはこちらです。 github.com 下記のようなconfigを手がかりにします。 pushed_configs$ grep -B 2 description mddo_network/configs/RegionA-CE01_config.txt ! interface Ethernet1 description to_RegionA-PE01_ge-0/0/2 -- ! interface Ethernet3 description to_RegionA-CE02_Ethernet3 -- ! interface Ethernet4 description to_RegionA-CE02_Ethernet4 -- ! interface Ethernet5 description to_RegionA-Acc01_Ethernet1 -- ! interface Ethernet6 description to_RegionA-Acc01_Ethernet2 -- ! interface Ethernet7 description to_RegionA-Acc01_Ethernet5 -- ! interface Ethernet8 description to_RegionA-Acc01_Ethernet6 上記のdescriptionを基にしてNetBoxへDevicesやInterfacesを登録します。 NetBoxのデータからしたL1トポロジの例です。 pushed_configs$ cat mddo_network/layer1_topology.json | jq . | head { "edges": [ { "node1": { "hostname": "RegionA-Acc01", "interfaceName": "Ethernet1" }, "node2": { "hostname": "RegionA-CE01", "interfaceName": "Ethernet5" 障害模擬データの作成 全パターンの単一障害を模擬するために、一部が欠損したBatfishへの入力データを作成します。与えられたL1トポロジからリンクを除きsnapshotとして登録します。除くリンクの記述は runtime_data.json 6 で行いました。また、後から参照できるようにどのリンクを除いたかを含めたsnapshotのメタデータをsnapshot_info.jsonとして保存します。 レポジトリはこちらです。 github.com 障害模擬として除くリンクは下記のように表現されます。 playground$ cat ./configs/pushed_configs_linkdown/mddo_network_01/batfish/runtime_data.json | jq . { "runtimeData": { "RegionA-Acc01": { "interfaces": { "Ethernet1": { "lineUp": false } } }, "RegionA-CE01": { "interfaces": { "Ethernet5": { "lineUp": false } } } } } モデルデータの生成 ここまででL1トポロジデータとBatfishによるL2/L3のデータが準備できました。これらをまとめてモデルデータとして生成します。データの表現方法は再利用性を考え RFC8345 7 を参考にした記述としました。RFC8345形式のデータはデータ構造が決まっていることで他のソフトウェアからもアクセスしやすいと考えられます。実際にブラウザ上で表示できるnetovizがあります。 データの生成は、L1トポロジはlayer1_topology.jsonのまま、Batfishのデータは一度csvに出力し、それらを読み込み再構築します。モデルデータの再構築をする中でインターフェースの齟齬やネットワークプレフィックスの差異など、正常に再構築できないデータ、つまり設定の誤りを検出できます。 レポジトリはこちらです。 モデルデータ生成 github.com RFC8345データの可視化 github.com Batfishのデータをcsvで出力すると、例えばIPアドレスとインターフェースの対応付けであれば下記の結果が得られます。 playground$ cat models/pushed_configs/mddo_network/ip_owners.csv | head ,Node,VRF,Interface,IP,Mask,Active 0,regionb-svr01,default,enp1s5,172.31.110.100,24,True 1,regionb-pe02,default,ge-0/0/4.0,10.1.2.2,30,True 2,regionb-svr02,default,enp1s4,172.31.20.100,24,True 各データを再構築したモデルデータは下記のような構造になります。 playground$ cat netoviz_model/pushed_configs_mddo_network.json | jq . | head -n 20 { "ietf-network:networks": { "network": [ { "network-id": "layer3exp", "network-types": { "mddo-topology:l3-network": {} }, "node": [ { "node-id": "regiona-ce01", "ietf-network-topology:termination-point": [ { "tp-id": "Vlan10#1", "supporting-termination-point": [ { "network-ref": "layer2", "node-ref": "regiona-ce01_Vlan10", "tp-ref": "Port-Channel2" } このデータはnetovizで各レイヤーごとに可視化できます。 データ操作のためのWeb UIサンプル データをもとにシミュレート操作を行うためにはJupyter Notebook等を利用できますが、決まった作業であればWeb API等を介してWeb UIを用意すると操作が簡便です。しかし、BatfishではPython実装のクライアントライブラリであるpybatfishのみの提供で、そのままではフロントエンド系のフレームワークとは相性が良くないです。そこでBatfishにWeb APIのラッパをかぶせることで、Batfish内部のコマンドを隠蔽し、可視化部分を開発しやすくしました。 ここではtracerouteを実行するWeb UIの実装例を紹介します。 batfish-wrapper BatfishにWeb API経由でアクセスできるラッパです。pybatfishをflask内で使用しています。Batfishの概念であるnetwork/snapshotに応じてURLを構成できるようにしています。 レポジトリはこちらです。 github.com Batfishに現在登録されているnetworkやsnapshotやinterfaceの一覧を取る例は次のような一連のAPIです。 $ curl -s http://192.168.23.31:15000/api/networks | jq . [ "batfish-test-topology", "pushed_configs", "pushed_configs_drawoff", "pushed_configs_linkdown" ] $ curl -s http://192.168.23.31:15000/api/networks/pushed_configs/snapshots | jq . [ "mddo_network" ] $ curl -s http://192.168.23.31:15000/api/networks/pushed_configs/snapshots/mddo_network/interfaces | jq . | head -n 20 [ { "addresses": [], "interface": "ae1", "node": "regionb-ce01" }, { "addresses": [], "interface": "Ethernet10", "node": "regiona-acc01" }, { "addresses": [ "172.16.5.1" ], "interface": "Port-Channel1", "node": "regiona-ce01" }, { "addresses": [], fish-tracer インターフェースを指定して、単数もしくは複数のsnapshotに対してtracerouteのシミュレート結果を表示できるWeb UIです。モデルデータをそのまま見て解析するのはやはり見づらく、開発時にも簡単に試せるこのUIがあって便利でした。NuxtJSで上記batfish-wrapperを呼んでいます。 レポジトリはこちらです。 github.com 下記のように複数snapshotに対してのtracerouteをグラフィカルに実行できます。 まとめ この記事では「Model Driven Network DevOps PJ」の概要紹介、configからモデルデータを作成するステップを解説しました。configからL1を復元し、障害模擬トポロジを機械生成し、BatfishでL2/L3情報も復元し、モデルデータとして出力しました。 本文中にもありますが、現在のシステムではまだまだモデルの利点を活かしきれる実装ではないため、引き続きモデルを活用できる実装を増やしています。取り組みにご興味ある方は 沖縄オープンラボラトリ までご連絡ください。 ブログリレーの次パート(TIS株式会社) に続きます。 次世代ICT基盤技術の実用化と普及を目指す研究機関。 https://www.okinawaopenlabs.org/ ↩ プロジェクトの紹介および2021年度の活動紹介資料のページ。 https://www.okinawaopenlabs.org/mdnd ↩ オープンソースのconfig解析ソフト。 https://www.batfish.org/ ↩ L1トポロジを表現する、Batfishの追加入力データの1つ。 https://batfish.readthedocs.io/en/latest/formats.html#layer-1-topology ↩ ブラウザによるネットワーク図生成フレームワーク。 https://github.com/codeout/inet-henge ↩ インターフェースの状態を示すBatfishの追加入力データの1つ。なお2022/03時点ではサンプルのデータ構造と実際に正しく読み込まれるデータ構造が違うので注意が必要。 https://batfish.readthedocs.io/en/latest/formats.html#runtime-interface-information ↩ ネットワークトポロジを表現するためのYANGデータモデルの定義。 https://datatracker.ietf.org/doc/html/rfc8345 ↩
アバター
はじめに こんにちは。プラットフォームサービス本部 データプラットフォームサービス部でSmart Data Platform(SDPF)のサービス企画を行っている安井・小野です。 閲覧頂きありがとうございます。我々は当社が提供しているSDPFサービスを組合わせた具体的な事例紹介をしています。 前回の 第2回 では、1点目としてオンライン化が広がる中でのバックアップの重要性(Micosoft365の安全なバックアップ)について、 例えばSDPFサービスを使った場合にはどのように実現できるのかについてご紹介しました。 2点目として現在社会問題になっているランサムウェア対策については、データ保存の観点で改ざん防止に対応したWasabiのオブジェクトロック機能を使用することで、 万一不幸にしてランサムウェア被害にあった際にも、安全なデータからシステムやシステムデータを復旧する方法について説明しました。 このように誰もが気軽にオンライン化されたサービスを容易に利用できる反面、外部から時間を問わずいつでもあらゆる側面からアクセスされるかもしれないという状態にもあり、 「自分達の資産は自分達で守る」取組みが一層求められています。 自社の情報資産を明確化し、適切な権限設定がされており、許可されたものだけが使用可能な状態を保つ。 それらを使用した形跡を記録化し、定期的に確認する。かつ各種のアクセス情報はログ情報として一定期間、保存する。 万一の問題発生時には、ログ情報を元に分析し、原因の追究、対策を実施する。 背景 オンライン化/リモート化に伴い、オフィス外からのリモートアクセスの利用が進む 時間と場所に制約を受けない働き方が進んできており、そのニーズの高まりから、SDPFネットワークサービスメニューの1つであるFlexible Remote Accessの利用が進んでいます。 Flexible Remote Access (FRA) とは外出先や自宅等あらゆる場所からセキュアにアクセスできるリモートアクセス&セキュリティ基盤です。 ログを取っていなかったら何も分からない!? 「適切な権限設定」に基づき「許可されたユーザー」がアクセスし「どのようなアクセスを行ったのか」、 「不正な通信をした形跡はないのか、または恐れがないのか」等、総合的なセキュリティ対策を実施して運用する必要があります。 その中でも「通信アクセスに関するログ情報」は重要なデータであり、リアルタイム分析をして対応する形態や、 万一の問題発生時に過去のログを参照して分析したり、活動証跡といった監査目的のためにログを保存しておく形態等があります。 SDPFサービスを利用してどのように実現したか 以下NTT Comのサービスを組み合わせて、FRAを使用して発生するログ情報を、クラウド上に長期間保存できる方法を実現しました。 閉域網から各種クラウドサービスに接続するインターコネクトサービス「 Flexible InterConnect (FIC) 」を使ったセキュアな通信経路の確立。 安価なストレージサービス「 Wasabiオブジェクトストレージ(Wasabi) 」の利用。 SDPFクラウド/サーバーのサーバーインスタンスを利用。 検証を通じた確認 実際に検証環境を作ってみた 今回は図のような検証環境を構築しました。特徴は以下となります。 FICを活用することにより、 FRAはもちろんのこと、WasabiやSDPFクラウド/サーバー等様々なサービスと閉域で接続可能な状態にしました。 ログ受信サーバーはSDPFクラウド/サーバーのサーバーインスタンス上に導入しました。 実際に検証を実施してみた FRAから発生するトラフィックログや脅威ログ、URLフィルタリングログといった各種ログをFIC経由でSDPFクラウド/サーバー上のログ受信サーバーに転送し、 別途作成したシェルスクリプトを用いることにより、日ごとにまとめたzipファイルを同サーバーのローカルディスクやWasabiに長期保存する検証をしました。 確認後の具体的な気付きポイント FRAには0系と1系の環境があり、それぞれのポータル上にて手動で設定する必要がありました。 ログ受信サーバーにはOSSであるFluentdを用いました。 Fluentdでは、Fluentd内の"タグ"に設定したキーワードを元にログを処理することが出来ます。 また、FRAでは送信するログのファシリティをそれぞれのsyslogサーバープロファイルごとに設定出来ます。 これらの機能を組み合わせることにより、例えばトラフィックログであれば「LOG_LOCAL0」を、脅威ログであれば「LOG_LOCAL1」をFRA側で設定します。 そして、Fluentd側では、0系および1系のホスト情報をタグに設定することにより、"タグ.ファシリティ"別にログの保存先ファイルを分けることが出来ました。 下記はFluentdの設定ファイルです。トラフィックログと脅威ログに関する設定のみ抜粋していますが、他のログも同様となります。 /etc/td-agent/td-agent.conf ###################################### # FRAからのsyslog受信 # ###################################### <source> @type syslog port 514 bind 0.0.0.0 tag FRA <transport tcp> </transport> <parse> message_format auto with_priority true </parse> </source> ###################################### # 受信ログをサーバー別にタグで分類 # ###################################### <match FRA.**> @type rewrite_tag_filter # 0系用タグ <rule> key host pattern 0系のホスト名 tag "0系のホスト名.${tag}" </rule> # 1系用タグ <rule> key host pattern 1系のホスト名 tag "1系のホスト名.${tag}" </rule> </match> ######################################################## # サーバー+ファシリティ別にログファイルへログを書き込み # ######################################################## # 0系 # トラフィックログ <match 0系のホスト名.FRA.local0.**> @type file path /var/log/FRA/FRA.0系のホスト名.traffic.*.log append true <buffer> timekey 1d # 1day timekey_wait 10m # 10mins deley for flush timekey_use_utc false # local </buffer> </match> # 脅威ログ <match 0系のホスト名.FRA.local1.**> @type file path /var/log/FRA/FRA.0系のホスト名.threat.*.log append true <buffer> timekey 1d # 1day timekey_wait 10m # 10mins deley for flush timekey_use_utc false # local </buffer> </match> ~ 途中割愛 ~ # 1系 # トラフィックログ <match 1系のホスト名.FRA.local0.**> @type file path /var/log/FRA/FRA.1系のホスト名.traffic.*.log append true <buffer> timekey 1d # 1day timekey_wait 10m # 10mins deley for flush timekey_use_utc false # local </buffer> </match> # 脅威ログ <match 1系のホスト名.FRA.local1.**> @type file path /var/log/FRA/FRA.1系のホスト名.threat.*.log append true <buffer> timekey 1d # 1day timekey_wait 10m # 10mins deley for flush timekey_use_utc false # local </buffer> </match> ~ 以降割愛 ~ FRAから送られてくるすべての種類のログを1ファイルにすることも可能ですが、ログの量が多くなり、視認性が下がってしまいます。 そのため、監査目的の観点からもこの機能の組み合わせは有効性が高いと感じました。 今回の検証では通常のWasabiバケットを利用しました。今後Wasabiではライフサイクルポリシー機能が追加されるという情報を得ているため、 機能実装後はランサムウェア対策としての観点からも有効なバケットに対して転送する確認をしていきます。 また、今後はSTEP2として、総合リスクマネジメントサービスの WideAngle との連携を検討していく予定です。 具体的な構成例や設定例の見える化 具体的な構成例や設定例、注意点等の知見についてはKnowledge Centerにて記載しておりますのでご確認ください。記載している情報は2022年3月時点の情報となります。 Knowledge Center 複合ソリューション構成ガイド FRAログ保存ソリューション 最後に 今後もクラウドへのデータ保存やデータ利活用を気軽に活用できるきっかけを活動を通じて発信していきます。 「ここの記載がわかりにくい」等のご意見や、「SDPFでこんなことができないか」等の疑問がありましたら、以下メールアドレスに対してお気軽にコメント等ご連絡ください。 sdpf-testbed-02@ntt.comにメールを送る ※お手数ですが@を全角文字から半角文字に置き換えてください。 ありがとうございました。
アバター
目次 はじめに インターンシップ参加にあたって 体験内容 IoT Connect GatewayとStorage転送機能についての理解 新サービスの提案 開発環境 カメラの開発 IoT Connect Gatewayの設定 Amazon Web Services上での画像認識 画像認識結果の可視化 インターンシップを終えた感想 トレーナーからのコメント はじめに こんにちは。この度、NTTコミュニケーションズの職業体験型インターンシップのエンジニアコースに参加させていただきました小林と申します。大学では情報科学を専攻しています。 趣味は折り紙・ゲーム・アニメなどで、特に折り紙は小さい頃から親しんできました。サークル活動としてはロボコンサークルに所属し、NHK学生ロボコンへの出場を目指してロボット製作をしています。 今回はNTTコミュニケーションズのインターンシップで、IoT Connect Gatewayというサービスを開発しているチームに参加いたしました。 インターンシップではIoT Connect Gatewayの検討/開発中の新規機能を使い、サービスの提案・開発をする機会をいただいたので、その体験談をまとめます。よろしくお願いいたします。 この記事を作成するにあたって、夏のインターン生の方の記事を参考にさせていただきました。 IoT Connect Gatewayを使ってみた 番外編 ~インターンシップでリリース前の機能を使って開発してみた~ インターンシップ参加にあたって 私は大学の授業で情報科学を学んでいるのですが、まだ2年ということもあって、学んでいることがどのように仕事の役に立つのかというイメージがもてませんでした。 そこで現場で本当に必要とされている能力は何かを実感できれば、大学での学びをより有意義なものにできるのではと考え、インターンシップに応募しました。 またサークルでのロボット製作活動を通して、得意分野や性格がそれぞれ異なる集団のなかでいかに自身の能力を発揮するか、円滑なコミュニケーションができる環境をどのようにつくるかについて考える機会があったため、チームでの開発にも興味がありました。 体験内容 2週間で体験したインターンシップの内容をまとめます。 1. IoT Connect GatewayとStorage転送機能の理解 1.1 IoT Connect Gatewayの概要 まず、本チームの開発サービスであるIoT Connect Gateway (ICGW)について教わりました。 図1 IoT Connect Gatewayの概要図 ICGWは以下の2つの機能を主軸としています。 プロトコル変換機能 IoTデバイスが送信するデータを暗号化 クラウドアダプタ機能 接続情報や鍵の交換を代理で実行 これらの機能により、デバイス側の負荷や暗号化設定の労力を増やすことなくセキュアな通信ができるうえ、クラウド側のインターフェースの仕様変更があった場合にも、ユーザによるデバイス側の操作が不要になるということを教わりました。 1.2 Storage転送機能の概要 次に、ICGWの検討/開発中の新規機能であるStorage転送機能についても教えていただきました。 図2 Storage転送機能の概要図 Storage転送機能はIoTデバイスからクラウドストレージへのデータを転送する機能として開発され、以下の4つの特徴を備えています。 セキュアプロトコル変換 HTTPメソッドでの基本的なオペレーション URLのパスを利用した柔軟なオブジェクト操作 カスタムメタデータの付与 これらの特徴により、IoTデバイスからのセキュアなファイルのアップロードが容易に実現できます。 なかでも、私が注目したのは カスタムメタデータの付与 ができることです。 カスタムメタデータとは、ICGWが保持しているIMSIやIMEI、デバイス名、日時といった情報のことを指します。例えば、この機能を用いてアップロードするファイル名に日時を含めることで、予期せぬ上書きを防ぐとともに送信時間を把握しやすくなります。 実際に私が提案したサービスにもこの機能を使用しました。 詳しくは 5. IoT Connect Gatewayの設定 と 6. Amazon Web Services上での画像認識 をご覧ください。 2. 新サービスの提案 インターン中では、"ICGWの「Storage転送機能」を利用した新サービスを考える"という課題に挑戦させていただくことになりました。 以下が、私の提案したサービス概要となります。 図3 提案したサービスの概要 今回考案した遠隔監視システムは大きく3つの特徴があります。 1つ目は、需要の高いシステムであるということです。昨今のCovid-19の流行や労働者不足により、少人数で広範囲をリアルタイムで監視できるシステムはあらゆる分野で求められていると考えられます。 2つ目は、通信量を少なく抑えられるような設計になっていることです。動体を検出したときの画像のみを送信する仕組みとなっているため、そのまま動画を送るより圧倒的に少ない通信量で運用できます。 3つ目は、他の用途に応用しやすいことです。動体検出を利用しているため監視だけでなく、交通量・歩行量調査や生態系の調査といった研究の分野での使用も想定できます。 3. 開発環境 システムの開発環境は以下の通りです。 機材、端末 MacBookPro(13-inch, M1, RAM 16GB) RaspberryPi Zero W V1.1 RaspberryPi Camera Rev 1.3 無線LANルータ(NEC Aterm MP02LN CW) 利用サービス IoT Connect Gateway Dashboard Amazon Simple Storage Service (S3) Amazon Web Services Lambda Amazon Rekognition Amazon CloudWatch 4. カメラの開発 RaspberryPiとRaspberryPi Cameraを用いて、動体検知のためのカメラを開発しました。 図4 実際に使用したRaspberryPiとRaspberryPi Camera 4.1 動体検出および画像抽出 RaspberryPiにインストールしたMotionというソフトウェアを使って、監視対象をモニタリングし、動体検出と画像の抽出をしました。 参考: https://www.handsonplus.com/electronic-works/how-to-use-motion-with-raspi/ 今回は被写体として私の弟に協力してもらいました。 図5 モニタリング中の映像と動体検出した画像 4.2 画像データの送信 続いて、Pythonを使って各画像ファイルのデータを送信します。 参考: https://www.handsonplus.com/electronic-works/how-to-use-motion-with-raspi/ 実際に使用したPythonのコードは以下の通りです。 import os import time from glob import glob # 画像送信 def send_img (attachments): # ファイルを送信 for attachment in attachments: os.system( "curl -v -X PUT -H 'Content-Type: image/jpeg' --data-binary @" \ + attachment \ + " http://***:8081/s3" ) # メイン関数 def main (): while ( True ): # 画像ファイルを探す attachments = sorted (glob( './*.jpg' )) # 添付ファイル確認 if len (attachments) >= 1 : # 画像送信 send_img(attachments) time.sleep( 10 ) # 送信後ファイル削除 os.system( 'sudo rm *.jpg' ) time.sleep( 10 ) main() jpg形式の各画像ファイルを読み込み、それぞれに対してcurlコマンドを実行することで送信の処理を行います。 IoTデバイスのストレージを圧迫しないように、送信し終わったファイルは削除します。 5. IoT Connect Gatewayの設定 次に、AWS S3にデータを送信するためのStorage転送機能の設定をICGWのコンソール画面から行いました。 以下が実際に投入した設定になります。 図6 Storage転送機能の設定 今回はFilepathに"$YYYY"や"$kk"というプレースホルダーを設定しています。これによりデータが送信された際に、ICGWの機能によってその時の日時データを埋め込めるようになっています。 この機能を活用することで、動体を検出した時間が明記されるだけでなく、ICGW側で一元的に日時データを管理ができます。 6. Amazon Web Services上での画像認識 次に、AWS上で画像認識処理をできるように設定しました。 今回はS3・Lambda・Rekognition・CloudWatchの4つの機能を連携させました。 6.1 S3上のファイル S3とは、AWSのクラウドストレージサービスです。 図7 S3上のファイル まずIoTデバイスからICGW経由でS3に画像ファイルが送られてきます。送信されたファイル名は、 5. IoT Connect Gatewayの設定 で設定したように送信日時となっていることが確認できます。 6.2 LambdaとRekognitionによる画像認識処理 S3に送られた画像は、LambdaとRekognitionによって画像認識処理されます。 Lambdaとは、サーバーレスでプログラムを実行できるサービスです。 図8 Lambdaでの処理 今回使用したLambdaは、トリガーをS3の送信先のディレクトリに設定しているので、画像が送られてくるとLambda上の関数が自動で実行されるようになっています。 関数の処理の流れは上記のフローチャートの通りです。 Lambdaでは送られてきた画像に対して、Rekognitionを用いて画像を解析した後に結果を出力します。 参考: https://acro-engineer.hatenablog.com/entry/2018/12/12/120000 以下が、実際に使用したLambda上の関数のソースコードです。 import os import boto3 s3 = boto3.resource( 's3' ) def lambda_handler (event, content): # S3にアップされた画像の情報を取得する。 bucket_name = event[ 'Records' ][ 0 ][ 's3' ][ 'bucket' ][ 'name' ] object_key = event[ 'Records' ][ 0 ][ 's3' ][ 'object' ][ 'key' ] file_name = os.path.basename(object_key) print ( "bucket_name=" + bucket_name) print ( "object_key=" + object_key) print ( "file_name=" + file_name) # 画像をRekognitionで解析する。 rekognition = boto3.client( 'rekognition' ) response = rekognition.detect_labels(Image={ 'S3Object' : { 'Bucket' : bucket_name, 'Name' : object_key } }) print (response) 6.3 CloudWatchでの画像認識結果の確認 画像認識の結果はCloudWatchで確認できます。 図9 CloudWatch上の画像認識結果 結果はログとして表示されるため少しわかりにくいですが、ラベル・確信度・対象物の頂点座標などが出力されます。 上記のログは一つの画像に対する画像認識結果です。 赤枠で囲まれた所には、"ラベルがPersonでその確信度が約97.4%"であるオブジェクトを認識できた旨が書かれています。 つまり、動体の正体として人間の存在を認識したことを意味します。 7. 画像認識結果の可視化 画像認識結果がログのままではわかりにくかったので、可視化してみました。 図10 画像認識結果の可視化 本来はLambda上で可視化処理まで行おうと考えていましたが、時間が足りなかったため、手元の検証端末にインストールしたOpenCVを用いました。 参考: https://acro-engineer.hatenablog.com/entry/2018/12/12/120000 以下に、可視化処理で使用したプログラムのソースコードを示します。 from urllib import response import cv2 # 加工するファイルの名前を代入 filename = '2022-23-123902.jpg' # CloudWatchでの画像認識結果を代入 response = { 'Labels' : [{ 'Name' : 'Clothing' , 'Confidence' : 99.97942352294922 , 'Instances' : [], 'Parents' : []}, { 'Name' : 'Apparel' , 'Confidence' : 99.97942352294922 , 'Instances' : [], 'Parents' : []}, { 'Name' : 'Suit' , 'Confidence' : 99.97110748291016 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Overcoat' }, { 'Name' : 'Coat' }, { 'Name' : 'Clothing' }]}, { 'Name' : 'Overcoat' , 'Confidence' : 99.97110748291016 , 'Instances' : [{ 'BoundingBox' : { 'Width' : 0.47841206192970276 , 'Height' : 0.8942648768424988 , 'Left' : 0.18658344447612762 , 'Top' : 0.05319703370332718 }, 'Confidence' : 81.37818908691406 }], 'Parents' : [{ 'Name' : 'Coat' }, { 'Name' : 'Clothing' }]}, { 'Name' : 'Coat' , 'Confidence' : 99.97110748291016 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Clothing' }]}, { 'Name' : 'Person' , 'Confidence' : 98.30304718017578 , 'Instances' : [{ 'BoundingBox' : { 'Width' : 0.42601126432418823 , 'Height' : 0.9695423245429993 , 'Left' : 0.21858000755310059 , 'Top' : 0.01187931653112173 }, 'Confidence' : 79.02320861816406 }], 'Parents' : []}, { 'Name' : 'Human' , 'Confidence' : 98.30304718017578 , 'Instances' : [], 'Parents' : []}, { 'Name' : 'Man' , 'Confidence' : 96.02997589111328 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Person' }]}, { 'Name' : 'Standing' , 'Confidence' : 79.08413696289062 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Person' }]}, { 'Name' : 'Face' , 'Confidence' : 64.02348327636719 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Person' }]}, { 'Name' : 'Tuxedo' , 'Confidence' : 56.30610656738281 , 'Instances' : [], 'Parents' : [{ 'Name' : 'Suit' }, { 'Name' : 'Overcoat' }, { 'Name' : 'Coat' }, { 'Name' : 'Clothing' }]}], 'LabelModelVersion' : '2.0' , 'ResponseMetadata' : { 'RequestId' : '***' , 'HTTPStatusCode' : 200 , 'HTTPHeaders' : { 'x-amzn-requestid' : '***' , 'content-type' : 'application/x-amz-json-1.1' , 'content-length' : '1427' , 'date' : 'Wed, 23 Feb 2022 12:39:04 GMT' }, 'RetryAttempts' : 0 }} # CV2で画像ファイルを読み込む。 np_image = cv2.imread(filename) height, width = np_image.shape[: 2 ] # ラベルの中から人と思われるものを探して四角で囲う。 for label in response[ 'Labels' ]: if label[ 'Name' ] not in [ 'People' , 'Person' , 'Human' ]: continue for person in label[ 'Instances' ]: box = person[ 'BoundingBox' ] x = round (width * box[ 'Left' ]) y = round (height * box[ 'Top' ]) w = round (width * box[ 'Width' ]) h = round (height * box[ 'Height' ]) cv2.rectangle(np_image, (x, y), (x + w, y + h), ( 0 , 0 , 255 ), 3 ) cv2.putText(np_image, label[ 'Name' ], (x, y - 9 ), cv2.FONT_HERSHEY_SIMPLEX, 1.5 , ( 255 , 255 , 255 ), 3 ) cv2.imwrite(filename + '_result.jpeg' , np_image) CloudWatchの出力ログから人間を表すラベルの頂点座標を抜き出し、その情報を基に作成したバウンディングボックスを元画像に貼りつけるという流れの処理を行いました。 インターンシップを終えた感想 最後に、本インターンシップを通して感じたことをまとめます。 1. 自力で調べる力の重要性を再確認できた RaspberryPiやICGW、AWS、Macは私にとって触れたことすらないものであり、2週間という短い期間でそれらすべてを扱うためには、自分で自発的に調べることがとても重要だと改めて実感しました。 また、特にIT業界は短いスパンで新しい技術が登場する変化の目まぐるしい業界であることからも、自力で調べる力は常に意識して伸ばしていきたいと思いました。 2. 大学での学びに対するモチベーションが得られた インターンシップに参加するまでは、大学で学んだことがどのように仕事に活かされるのかイメージできませんでした。 特にサークルでのロボット製作は、"あくまでサークル活動でお遊びである"という意識があり、自分がしてきた活動に自信を持てないことがありました。 しかし、実際に業務内容を経験させていただいたことで、大学での自分の活動が確かに仕事に結びつくと実感でき自信につながったとともに、これからの大学での学びに対する絶大なモチベーションを獲得できたと感じています。 3. チーム開発における"良い雰囲気"を実感できた インターンシップに参加することが決まったとき、技術面・経験面でも未熟な大学2年の自分が、社員の方々にご迷惑をかけずにしっかり業務をこなせるのか非常に心配でした。しかし、チームメンバーの方々は緊張して硬くなった私をアットホームな雰囲気で暖かく迎えてくださったおかげで、楽しく仕事ができました。 特に、私が自力ではどうしても解決できない問題に直面した際、気兼ねなく質問できるようにしてくださいました。 ICGW開発チームの方々からは、技術だけでなく、チーム開発をするうえで大切なチーム内の雰囲気がどのようなものであるかを身をもって学ばせていただきました。 2週間という短い間でしたが大変お世話になりました。ありがとうございました! トレーナーからのコメント 小林さんのトレーナーを務めたICGWチームの岩田です。 今回のインターンシップでは、ICGWのStorage転送機能を使って新規サービス提案に取り組んでいただきました。RaspberryPiやICGW、AWSといった小林さんにとってあまり馴染みのない技術領域が多かったかもしれないですが、その中でも自分なりのウィルを持って取り組む姿勢は印象的でした。また、大学2年生ながらもすでに大学での勉学の意義や将来を見据えており、素晴らしい姿勢だと感心しました。今後もその姿勢を大切に、学業やロボコンに打ち込んでいただけたらと思います。このインターンで得られた経験を今後の小林さんの大学生活に役立ててもらえると我々も嬉しいです! 2週間お疲れ様でした!
アバター
はじめに はじめまして。イノベーションセンター所属の @sublimer です。 2021年4月に新卒として入社し、現在はWebRTCプラットフォーム 「SkyWay」 の開発・運用の業務に取り組んでいます。 また、個人開発としてWebアプリケーションを作ったり、TURNサーバー(RFC5766)をC#で実装したりしています。 他にも、自宅サーバを運用したり、ネットワーク機器を触ってみたりもしています。 ちなみに推しRFCは RFC1149 です。 今回は、現在 SkyWay Beta として提供中の新しいSkyWayのサービス監視機能を構築した話を紹介します。 SkyWayのサービス監視における課題 まずはじめに、現行のSkyWayが抱えていたサービス監視の課題について説明します。 現在のサービス監視には、以下の3つの課題がありました。 VMを管理するコスト 障害発生時の架電 ログ調査の手間 1. VMを管理するコスト 現行のSkyWayでは、管理用のVM上で Zabbix を動かしてサービス監視を行っています。 Zabbixは非常に優れた監視用ソフトウェアですが、VMを管理する手間がかかるほか、監視サーバーのメンテナンスをしている間はサービスの監視ができないという課題がありました。 2. 障害発生時の架電 現行のSkyWayでは、社内の監視チームが障害時の状況把握や関係者への連絡といった一次対応などを行っていました。 新しいSkyWayでは、人に頼らず電話をかける仕組みを導入し、障害発生時の対応を自動化・省力化することとなりました。 Zabbixには、障害発生時に電話をかけるような機能は無いため、何らかのSaaSと連携させる必要がありました。 3. ログ調査の手間 SkyWayのアクセスログ・アプリケーションログは、 Fluentd を用いて Google Cloud Storage(GCS) にアップロードされる仕組みとなっています。 ログ調査を行う際は、GCSから該当のログをダウンロードし、手元のマシンでgrepして調査しています。 調査の際は複数のVMのログを横断的に調査したい場合もあり、その都度ログをダウンロードして調査するのはかなり手間がかかっていました。 以上の課題を踏まえ、いくつかのSaaSやOSSを比較検討した結果、 Datadog 、 PagerDuty 、 Google Cloud Logging を用いて、新しいSkyWayのサービス監視を行うことにしました。 使用した各SaaSの役割 今回使用した各SaaSについて、その役割を簡単に説明します。 Datadog VMのCPU使用率やメモリ使用量といった時系列の数値データ、「メトリクス」を収集します。 また、事前に設定した条件を満たした場合はアラートとして通知します。 メトリクスは、VMにインストールしたDatadog Agentから定期的に送信されるほか、Web APIを呼び出すことで、任意のデータを記録することもできます。 PagerDuty DatadogやGoogle Cloud Loggingと連携させ、アラートの内容をモバイルアプリや電話で通知します。 予めオンコールシフトやエスカレーションポリシーを設定することで、柔軟な通知が実現できます。 また、誰が障害対応したのか、どういった対応をしたのかといった情報を記録でき、障害対応の振り返りに役立てることもできます。 Google Cloud Logging ログを集約・転送するソフトウェアであるFluentdから送られたアプリケーションログやアクセスログを記録します。 ログを構造化しておくことで、Google Cloud Loggingで柔軟にフィルタリング・検索ができます。 また、複数のVMのログを一箇所に集約できるため、横断的にログを検索できます。 構築したサービス監視の全体像 アラート通知のフローは、以下の図のように設定しています。 もし、メトリクスやログの内容に異常があれば、PagerDutyにアラートの情報が送られ、モバイルアプリや電話経由で通知が行われます。 基本的には、まずアプリ経由で通知し、一定時間アクションがない場合に電話をかけるようにしています。 一方で、サービスの運用に致命的な影響を与えるアラートに関しては、即座に電話をかけるように設定しています。 これにより、DevOpsチームが即座に障害の対応ができるようになりました。 また、メトリクスのグラフを共有するために、DatadogからSlackへの通知も設定しています。 以前はZabbixのスクリーンショットを手動で撮影してSlackに共有していましたが、DatadogとSlackの連携設定により、 スラッシュコマンド などで手軽にグラフ画像の共有ができるようになりました。 さらに、メトリクスとアラートの情報を紐付けて表示するダッシュボードを作ることで、障害時の各種メトリクスの状態を確認しやすくなりました。 DatadogのカスタムAgentチェックを効率的に開発する 今回Datadogを導入した際に得られたtipsを共有します。 Datadogでは、デフォルトでCPU使用率やメモリ使用量などのメトリクスを送信できますが、実際のサービス監視を行う際は、これらのメトリクスだけではカバーしきれない場合があります。 その場合は、カスタムAgentチェックのスクリプトを作成することで、任意の値をメトリクスとして送信できます。 カスタムAgentチェック用のスクリプトの動作確認にはDatadog Agentをインストールする必要があるのですが、このインストールの手間を減らすため、 Docker Composeを用いて動作確認ができる仕組みを作りました。 以下のコマンドでPythonの仮想環境を作成し、カスタムAgentチェックのスクリプトを ./src/custom_example.py に、設定ファイルを ./conf.d/custom_example.yml 記述します。 $ python -m venv venv $ . ./venv/bin/activate $ pip install datadog-checks-base ./src/custom_example.py import random from datadog_checks.base import AgentCheck __version__ = "1.0.0" class CustomExample (AgentCheck): def check (self, instance): self.gauge( "custom_example.value" , random.randint( 0 , 10 ), tags=[ "env:dev" ], ) ./conf.d/custom_example.yml instances : [{}] その後、以下の docker-compose.yml を用いてDockerコンテナを起動します。 version : '3' services : agent : image : datadog/agent volumes : - ./src:/etc/datadog-agent/checks.d - ./conf.d:/etc/datadog-agent/conf.d environment : - DD_API_KEY=dummy-api-key DD_API_KEY の環境変数を設定しないとDatadog Agentが起動しないため、ダミーの文字列を入れています。 Dockerコンテナが起動した後、ホスト側で以下のコマンドを実行することで、容易にスクリプトの動作確認ができます。 $ docker-compose exec agent agent check custom_example --table === Series === METRIC TYPE TIMESTAMP VALUE TAGS custom_example.value gauge 1646983404 5 env:dev ========= Collector ========= Running Checks ============== custom_example (1.0.0) ---------------------- Instance ID: custom_example:d884b5186b651429 [OK] Configuration Source: file:/etc/datadog-agent/conf.d/custom_example.yml Total Runs: 1 Metric Samples: Last Run: 1, Total: 1 Events: Last Run: 0, Total: 0 Service Checks: Last Run: 0, Total: 0 Average Execution Time : 0s Last Execution Date : 2022-03-11 07:23:24 UTC (1646983404000) Last Successful Execution Date : 2022-03-11 07:23:24 UTC (1646983404000) おわりに 新しいSkyWayでのサービス監視の概要とtipsを紹介しました。 元々学生時代からインフラの監視技術に興味があり、自宅サーバーなどでZabbixや Prometheus を使っていましたが、商用サービスの監視に関わる機会は無く、入社前から商用サービスの監視に挑戦してみたいと思っていました。 そのことを上長との1on1で話したところ、SkyWayのサービス監視について検証や構築を任せていただけました。新入社員であってもいろいろなことに挑戦できるSkyWayのチームは、とても居心地が良いなと思っています。 今回、一からサービス監視の仕組みを作りましたが、新しいSkyWayのサービス監視は始まったばかりです。 最強で最高のサービス監視体制を目指して、引き続き改善に取り組んでいきたいと思います。 最後に少しだけ宣伝です。 現在提供中の SkyWay Beta は、アカウント登録をするだけで、個人・法人問わずどなたでも使うことができます。 これまでのSkyWayには無かった機能も今後実装される予定ですので、ぜひ一足早く体験してみてください! フィードバックなどもお待ちしています!! 参考サイト カスタム Agent チェック Datadog Docs
アバター
はじめに こんにちは、インターンシップ生の 金谷 です。 2022年2月に2週間ほどNTTコミュニケーションズのインターンシップに参加させていただきました。 普段は大学院やWIDEプロジェクト、アルバイトなどで SRv6 BGP-EPEなどオーバレイネットワーク技術の研究開発をしています。 インターンシップには「 次世代のサービスを生み出す検証網 Testbedの設計構築業務 」というテーマで、2022年2月14日から25日までの2週間参加しました。 具体的にはFRRouting(FRR)というOSSのソフトウェアルータにBGP-LSという機能を実装するという内容に取り組みました。 この記事では、その体験談を記載します。 インターンシップに参加するまで NTT Comという会社に非常に興味を持っていたため、会社の雰囲気を知りながら面白い開発が出来そうだと思い、参加を決めました。 また、インターンシップを通じて入社後どのプロジェクトに所属するかの参考にしたいと思ったのも参加を決めた理由の1つです。 インターンシップで取り組んだこと インターンシップでは、主にFRRというOSSのソフトウェアルータにBGP-LSの機能を追加し、複数のメーカーの機器(Cisco、Juniper)と相互接続できるよう実装に取り組みました。 現在NTT Comでは大規模なMulti-AS Segment Routing(SR)網の構築、運用を行なっており、その網上で効率的な経路制御を実現するためPath Computation Element(PCE)によるコントローラ制御を目指しています。 Multi-AS SR網はマルチベンダに構成されており、SRv6環境の一部では拡張性を考慮しOSSのFRRを採用しています。 このFRRをコントローラ制御の対象に含めるため、FRRにBGP-LSの機能を実装し、トポロジ情報をコントローラに送信することが求められています。 また、Multi-ASの環境ではASの接続部分でBGP-EPEを行いたいという要求もあり、BGP-EPEの情報をBGP-LSで広告できること( RFC9086 , draft-ietf-idr-bgpls-srv6-ext-09 )なども鑑みて、BGP-LSは今後必要な機能であると考えられています。 私はBGP-LSというプロトコルの名前は知っていましたが、細かい内容については知らなかったため、始めはプロトコルの理解を目的として RFC7752 , 9085 などを読み込んでいきました。 次にFRRの開発も少しかじったことはありましたが、BGPについての開発は初めてでしたので、どのファイルを読めば良いのかが分からず、printデバッグをしながら手探りでFRRの構成を読み解いていきました。 最終的にはFRRにBGP-LSのOpenメッセージを実装し、IOS-XRやJunosとの間でBGPのステートをEstablishedさせることができました。 以下では、BGP-LSの機能や必要性、インターンシップでの開発内容を具体的にご紹介します。 BGP-LSとは BGP-LSとはBGP Link Stateの略称で、IGPのリンクステート情報や、トラフィックエンジニアリング(TE)情報を伝達するためのプロトコルです。 BGP-LSを利用すれば、複数のIGPドメインのトポロジ情報をPCE等のコントローラに収集することが容易となるため、NTT Comが現在検証を進めているSegment Routingを用いたMulti-AS TEに活用できると考えられます。 上図にPCEのユースケースを示します。PCEはPEからBGP-LSによってトポロジ情報を吸い上げて経路を計算し、PCEPでPEに経路を配布します。 これにより、発行済みの経路を一元管理できるようになると共に、帯域や信頼性など経路の状態を考慮した複雑なTEが行えるようになり、効率的な運用を実現できます。 FRRの構造 FRRouting とは、LinuxやBSDなどの上で動くOSSのソフトウェアルータです。 以下の図がFRRの基本構造となっています。 FRRはLinux Server上で動くアプリケーションとなっており、vtyshでユーザの操作をCLIで取得し、その情報をisisdなどのプロトコルデーモンに送信します。 isisdにはLink State Database(LSDB)が存在し、IS-ISというIGPプロトコルによって交換された各Link State情報が格納されています。 このLSDBの情報をpathd内に存在するTraffic Engineering Database(TED)にZAPIを利用してZebra経由でエクスポートし、TEDの情報を元にbgpdがBGP Databaseを変更します。 そして、bgpdがBGP-LSにより他のルータとの間でBGP Databaseとの情報を共有します。 ネットワークスタックの利用にあたっては、ZebraがNetlinkを利用してユーザ空間の情報をカーネル空間に伝達することで行われます。 実装内容 BGP-LSによってLink State情報を複数ルータ間で交換するためには、前提としてルータ間でBGP-LSの接続を確立していなければなりません。BGP-LSの接続を確立するためには、以下の手順が必要となります。 対象となるBGPスピーカ同士でTCPセッションを確立する BGP-LSを示すCapabilityの含まれたBGP Openメッセージを送信/受信する BGP Keep Aliveメッセージを受信する これらを行うことにより、BGPが確立されたステートとなり、BGP-LSのUpdateメッセージにより実際にBGP-LSのLink State情報を相互に交換し合うことができるようになります。 上記の手順をもう少し厳密に記述すると以下の図のようになります。 今回私はインターンシップ期間でBGP-LSが既に実装されているIOS-XRやJunosと、新たに実装を追加したFRRとの間でBGP-LSのEstablishedを実現しました。 初めに、FRRにはBGP-LSのAFIやSAFIが定義されていなかったため、RFCに準拠した定義を実施しました。この際、Zebraの認識するAFI/SAFIとIANAによって定義されているAFI/SAFIの値が異なるため、変換設定も必要となります。 続いて、TCP connectionがOpenになると、BGPのステートはConnectに遷移し bgp_reads_on() と bgp_open_send() という2つの関数が呼ばれます。 bgp_reads_on() では、BGP Openメッセージを受信すると適切にパースし、ピアから受信したOpenメッセージの情報を構造体に格納します。 bgp_open_send() では、Openメッセージをピアに送信します。 これら2つの関数は非同期に実行されます。 bgp_open_send() 関数が呼ばれた際はOpen Sentステートに遷移し、 bgp_open_send() と bgp_reads_on() の双方が行われた際にはOpen Confirmステートに遷移します。 あとは既存のBGP Keep Aliveメッセージによって自動でEstablishedステートへと遷移することとなります。 結果 以下がFRR側でBGP-LSのステートを表示させた結果です。 Stateが (Policy) となっています。 これはBGPステートがEstablishedかつ、Export Filterがないという意味であるため、想定通りBGP-LSがEstabsliehdになっていることが確認できました。 また、以下がIOS-XRやJunos側でBGP-LSのステートを表示させた結果です。 こちらも双方でBGPステートが Established となることが確認できました。 本インターンでは期間の関係でLSDBのExport機能や関連するshowコマンドの実装まではできませんでしたが、FRRとのコントローラ連携の実現を目指し、今後も開発を進めて行きます。 インターンシップの感想 2週間で名前程度しか知らないプロトコルの調査から始め、現在のBGP-LSの実装状況の調査やインターンシップのゴールをどこに置くかを考え、 ゴールを実現するために1000行以上のソースコードを読み必要な箇所に機能を追加していくという経験は、環境構築や多数のファイルを行き来するコード理解など大変な部分もありましたが最終的には非常に楽しかったです。 メンターの三島さんには頻繁に相談に乗っていただき、そこでプロトコルやFRRの概念の理解の補助をしていただいたり、 ソースコードを読んでいて詰まった時のネクストアクションを提示してくださったりなど、僕がインターンシップを完走出来るよう最大限フォローしていただいたことを大変感謝しています。 フルオンラインのインターンでしたが、業務以外でインターンシップの配属チームの社員さんとオンラインでお酒を飲みながらお話をして、チームの雰囲気を知れる場を用意してくださったり、 自分が興味のある別チームの方とミーティングをセッティングして業務内容を聞く会を開催してくださったりと、 インターンシップを通じて入社後のイメージを掴むための場を最大限提供してくださったことを大変感謝しています。 最後になりますが、本インターンシップでたくさんの貴重な経験をさせていただき本当にありがとうございました。 メンターからのコメント メンターを担当したイノベーションセンターの三島です。2週間のインターンシップお疲れ様でした。 初日の説明日・最終日の成果報告日や祝日を除くと1週間に満たない作業日でしたが、 NTT Comの背景理解とテーマ設定から始まり、プロトコルのRFC調査を通じた理解やFRRのコードリーティングと実装、検証環境構築や相互接続試験まで検証業務を一連で進めていただきました。 応用的なルーティングプロトコルの機能開発という難易度の高いテーマであったにも関わらず、調査から検証までを自ら進めて成果を出していただけたこと、とても嬉しく思っています。 現在NTT Com イノベーションセンターではMulti-AS SRという独自の大規模SR網を運用しており、BGP-LSはコントローラ連携による効率的な運用のための重要な技術です。 今後も開発を続け、是非FRRへのコントリビューションなど、更なる成果をあげられればと考えています。 より面白いネットワークを実現し価値を提供できるよう、我々も頑張ります! 今回のインターンシップで得た経験が、今後の金谷さんの研究や取り組みに役立てば幸いです。 改めて、インターンシップへの参加とご活躍ありがとうございました!
アバター
はじめに こんにちは、イノベーションセンターの鈴ヶ嶺です。 engineers.ntt.com engineers.ntt.com 第1回、第2回に引き続きAWS Outposts ラックについて紹介していきます。 本記事では、 Terraform  を用いてOutposts上でオンプレ環境からのみ管理・アクセス可能なPrivate Elastic Kubernetes Service(EKS) を構築する方法を紹介します。 Terraform Terraformとは、 HashCorp が提供するインフラをコード化して自動構築を可能とするInfrastructure as Code(IaC)を実現するためのツールです。OutpostsをTerraformでIaC化するためには、対応した各リソースに outpost_arn を渡すことでOutposts上のリソースが作成されます。 以下はsubnetの一例です。 1 resource "aws_subnet" "main" { vpc_id = aws_vpc.main.id cidr_block = "10.0.3.0/24" outpost_arn = "arn:aws:outposts:ap-northeast-1:1234567890:outpost/op-1234567890" } outpost_arn - (Optional) The Amazon Resource Name (ARN) of the Outpost. CDKと比較してTerraformはAWS APIを利用しているのでOutpostsのリソースは比較的対応されていると思われます。今後新しいリソースがOutpostsで利用可能になった場合も対応スピードは早いと予想されます。ただ一方で、CDKのような高レベルなリソース作成をすることが難しいのでリソースやAPIの仕様を把握する必要があります。またリソースの認証情報・状態管理なども考える必要があります。 オンプレ環境上でマネージドk8sサービスを構築するアーキテクチャ 上記のようなオンプレ環境においてもマネージドk8sサービスを利用するアーキテクチャを構築します。Outposts上にAuto Scale Groupを設置してセルフマネージド型ノードとしてそれぞれEC2をEKS Clusterに登録させます。Outpostsとオンプレ環境との通信は全てLocal Gateway経由で接続されます。 この構成の特徴としてEKS ClusterのAPIサーバをPrivateアクセスのみ許可することで、Bastion(踏み台)経由でのみクラスタの操作を可能とするセキュアな点が挙げられます。さらに、アプリケーションもオンプレ環境のみからアクセス可能なため社内にのみ公開する秘匿性の高いシステムを構築できます。 実装 以下のような構成で、それぞれのtfファイルにリソースを定義します。 main.tf Providerや各変数などの全体を記載 network.tf ネットワークに関するものを記載 eks_cluster.tf EKS Clusterに関するものを記載 eks_node.tf EKS Nodeに関するものを記載 security_group.tf Security Groupによる通信の許可に関するものを記載 bastion.tf 踏み台サーバに関するものを記載 output.tf k8sアプリケーションのデプロイ時に使用するものを記載 main.tf terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 3.0" } } } provider "aws" { region = var.region } # Name tag に一部使用する値 variable "name" { default = "outposts-eks-sample" } # EKS Node のインスタンス variable "instance_type" { default = "m5.large" } # EKS Node のdisk(GB) variable "disk_size" { default = 100 } variable "region" { default = "ap-northeast-1" } # EKS Node の望ましいノード数 variable "desired_node" { default = 3 } # EKS Node の最低ノード数 variable "min_node" { default = 3 } # EKS Node の最高ノード数 variable "max_node" { default = 3 } # Local Gateway経由からEKSにアクセス可能なオンプレ環境のCIDR variable "local_cidr_blocks" { default = [ "192.168.0.0/16" ] } locals { # outpostのARN outpost_arn = "arn:aws:outposts:ap-northeast-1:1234567890:outpost/op-1234567890" # Local GatewayのID lgw_id = "lgw-1234567890" # Local GatewayのルートテーブルのID lgw_rtb_id = "lgw-rtb-1234567890" # 顧客所有のIPアドレス customer_owned_ipv4_pool = "ipv4pool-coip-1234567890" # GPUを使用しているかどうか gpu = replace (var.instance_type, "g4dn" , "!" ) != var.instance_type } # Bastion, EKS Nodeにアクセス可能な鍵 resource "aws_key_pair" "key" { key_name = "$ { var.name } -key" public_key = file ( "~/.ssh/id_rsa.pub" ) } main.tfには AWS Provider と各変数、Bastionの鍵を設定します。 OutpostsのARN outpost_arn や Local GatewayのID lgw_id などはここに設定しておきます。 Outposts上でもAuto Scaling groupは仕様可能です。その設定に用いてる max_node , min_node , desired_node の関係は次の画像のようになっております。 2 今回は3ノードの構成で作成します。 network.tf resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" # VPCエンドポイントを有効にするためにdns hostnames, supportを設定 enable_dns_hostnames = true enable_dns_support = true tags = { Name = "vpc-$ { var.name } " } } # EKS Clusterのsubnet resource "aws_subnet" "cluster01" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" tags = { Name = "subnet-cluster01-$ { var.name } " } } # EKS Clusterのsubnet resource "aws_subnet" "cluster02" { vpc_id = aws_vpc.main.id cidr_block = "10.0.2.0/24" tags = { Name = "subnet-cluster02-$ { var.name } " } } # Outpostsのsubnet resource "aws_subnet" "outposts_subnet01" { vpc_id = aws_vpc.main.id cidr_block = "10.0.3.0/24" outpost_arn = local.outpost_arn availability_zone = "ap-northeast-1a" tags = { Name = "subnet-outposts-subnet01-$ { var.name } " "kubernetes.io/cluster/$ { aws_eks_cluster.main.name } " = "owned" "kubernetes.io/role/elb" = "1" } } # デフォルトルートをLocal Gatewayに設定 resource "aws_route_table" "local_gateway" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" local_gateway_id = local.lgw_id } tags = { Name = "local-gateway-route-table-$ { var.name } " } } resource "aws_route_table_association" "outposts_subnet01" { subnet_id = aws_subnet.outposts_subnet01.id route_table_id = aws_route_table.local_gateway.id } data "aws_ec2_local_gateway_route_table" "main" { outpost_arn = local.outpost_arn } resource "aws_ec2_local_gateway_route_table_vpc_association" "main" { local_gateway_route_table_id = data.aws_ec2_local_gateway_route_table.main.id vpc_id = aws_vpc.main.id } # EKS Nodeに設定するEIPのPool resource "aws_eip" "main" { count = var.max_node customer_owned_ipv4_pool = local.customer_owned_ipv4_pool tags = { Name = "eip-node-$ { count.index } " } } # Bastion(踏み台)のEIP resource "aws_eip" "bastion" { customer_owned_ipv4_pool = local.customer_owned_ipv4_pool tags = { Name = "eip-bastion-$ { var.name } " } } # 初期起動時にEKS Node自身がEIPを自ら設定するためのEC2エンドポイントを設定 resource "aws_vpc_endpoint" "ec2" { vpc_id = aws_vpc.main.id service_name = "com.amazonaws.$ { var.region } .ec2" vpc_endpoint_type = "Interface" subnet_ids = [ aws_subnet.outposts_subnet01.id ] security_group_ids = [ aws_security_group.vpc_endpoint_ec2.id, ] private_dns_enabled = true } network.tfにはネットワークに関するリソースを定義します。 ネットワーク構成として、Public AWS上にsubnetを2つ、Outposts上にsubnetを1つ作成します。OutpostsのsubnetにEKSのWorker Nodeが設置されます。基本的にOutpostsのsubnetはデフォルトルートをLocal Gatewayに設定することでインターネットなどのアクセスはオンプレ環境経由で接続されるようにします。これにより社内のネットワークポリシーの適用や監視などを可能とします。また、Local Gatewayに接続するためには顧客所有のIPアドレスcustomer owned IP address(CoIP)をアタッチする必要があるためEKS Node、Bastion用のCoIPのElastic IP アドレス(EIP)を作成しておきます。 eks_cluster.tf # EKS Cluster resource "aws_eks_cluster" "main" { name = var.name role_arn = aws_iam_role.cluster_role.arn vpc_config { security_group_ids = [ aws_security_group.cluster.id ] subnet_ids = [ aws_subnet.cluster01.id, aws_subnet.cluster02.id ] # Private Accessのみに設定してBastion経由でのみ操作する endpoint_public_access = false endpoint_private_access = true } depends_on = [ aws_iam_role_policy_attachment.eks_cluster_policy, aws_iam_role_policy_attachment.eks_service_policy, aws_iam_role_policy_attachment.eks_vpc_resource-controller, ] } resource "aws_iam_role" "cluster_role" { name = "eks-cluster-role-$ { var.name } " assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "eks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF } resource "aws_iam_role_policy_attachment" "eks_cluster_policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" role = aws_iam_role.cluster_role.name } resource "aws_iam_role_policy_attachment" "eks_service_policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy" role = aws_iam_role.cluster_role.name } resource "aws_iam_role_policy_attachment" "eks_vpc_resource-controller" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController" role = aws_iam_role.cluster_role.name } # ALBを使用するためのIAM Policy resource "aws_iam_policy" "aws_load_balancer_controller" { name = "alb-policy-$ { var.name } " # from https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.2.1/docs/install/iam_policy.json policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iam:CreateServiceLinkedRole", "ec2:DescribeAccountAttributes", "ec2:DescribeAddresses", "ec2:DescribeAvailabilityZones", "ec2:DescribeInternetGateways", "ec2:DescribeVpcs", "ec2:DescribeSubnets", "ec2:DescribeSecurityGroups", "ec2:DescribeInstances", "ec2:DescribeNetworkInterfaces", "ec2:DescribeTags", "ec2:GetCoipPoolUsage", "ec2:DescribeCoipPools", "elasticloadbalancing:DescribeLoadBalancers", "elasticloadbalancing:DescribeLoadBalancerAttributes", "elasticloadbalancing:DescribeListeners", "elasticloadbalancing:DescribeListenerCertificates", "elasticloadbalancing:DescribeSSLPolicies", "elasticloadbalancing:DescribeRules", "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTargetGroupAttributes", "elasticloadbalancing:DescribeTargetHealth", "elasticloadbalancing:DescribeTags" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "cognito-idp:DescribeUserPoolClient", "acm:ListCertificates", "acm:DescribeCertificate", "iam:ListServerCertificates", "iam:GetServerCertificate", "waf-regional:GetWebACL", "waf-regional:GetWebACLForResource", "waf-regional:AssociateWebACL", "waf-regional:DisassociateWebACL", "wafv2:GetWebACL", "wafv2:GetWebACLForResource", "wafv2:AssociateWebACL", "wafv2:DisassociateWebACL", "shield:GetSubscriptionState", "shield:DescribeProtection", "shield:CreateProtection", "shield:DeleteProtection" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2:AuthorizeSecurityGroupIngress", "ec2:RevokeSecurityGroupIngress" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2:CreateSecurityGroup" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2:CreateTags" ], "Resource": "arn:aws:ec2:*:*:security-group/*", "Condition": { "StringEquals": { "ec2:CreateAction": "CreateSecurityGroup" }, "Null": { "aws:RequestTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "ec2:CreateTags", "ec2:DeleteTags" ], "Resource": "arn:aws:ec2:*:*:security-group/*", "Condition": { "Null": { "aws:RequestTag/elbv2.k8s.aws/cluster": "true", "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "ec2:AuthorizeSecurityGroupIngress", "ec2:RevokeSecurityGroupIngress", "ec2:DeleteSecurityGroup" ], "Resource": "*", "Condition": { "Null": { "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:CreateLoadBalancer", "elasticloadbalancing:CreateTargetGroup" ], "Resource": "*", "Condition": { "Null": { "aws:RequestTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:CreateListener", "elasticloadbalancing:DeleteListener", "elasticloadbalancing:CreateRule", "elasticloadbalancing:DeleteRule" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:AddTags", "elasticloadbalancing:RemoveTags" ], "Resource": [ "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" ], "Condition": { "Null": { "aws:RequestTag/elbv2.k8s.aws/cluster": "true", "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:AddTags", "elasticloadbalancing:RemoveTags" ], "Resource": [ "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" ] }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:ModifyLoadBalancerAttributes", "elasticloadbalancing:SetIpAddressType", "elasticloadbalancing:SetSecurityGroups", "elasticloadbalancing:SetSubnets", "elasticloadbalancing:DeleteLoadBalancer", "elasticloadbalancing:ModifyTargetGroup", "elasticloadbalancing:ModifyTargetGroupAttributes", "elasticloadbalancing:DeleteTargetGroup" ], "Resource": "*", "Condition": { "Null": { "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" } } }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:RegisterTargets", "elasticloadbalancing:DeregisterTargets" ], "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:SetWebAcl", "elasticloadbalancing:ModifyListener", "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:RemoveListenerCertificates", "elasticloadbalancing:ModifyRule" ], "Resource": "*" } ] } EOF } eks_cluster.tfでは、EKS Clusterに関するリソースを定義しています。 ここでは、EKS ClusterのPrivate Accessを設定することで内部からのみAPIサーバにアクセス可能となります。この設定をすることでよりセキュアなk8s環境が実現できます。運用の際にはBastion経由でkubectl, helmなどの操作をします。 eks_node.tf locals { # 初期起動時にEKS Node自身がEIPを自ら設定してオンプレ環境に対して通信する node_user_data = <<EOF #!/bin/bash set -o xtrace export AWS_DEFAULT_REGION=$ { var.region } instance_id=$(curl 169 . 254 . 169 . 254 /latest/meta-data/instance-id/) # EIP Check function function check_eip () { RES=`aws ec2 describe-addresses --filters Name=allocation-id,Values=$ 1 | jq .Addresses [ 0 ] .InstanceId` if [ "$RES" = "null" ] ; then return 0 fi return 1 } eips= "$ { join ( " " , aws_eip.main.*.id) } " # EIP Attach for eip in $eips; do check_eip $eip if [ $? = 0 ] ; then aws ec2 associate-address --no-allow-reassociation --instance-id $instance_id --allocation-id $eip if [ $? = 0 ] ; then break fi fi done # EKS Node Bootstrap /etc/eks/bootstrap.sh $ { aws_eks_cluster.main.name } EOF node_role_name = "node_role_$ { var.name } " } data "aws_ssm_parameter" "eks_cpu_ami" { name = "/aws/service/eks/optimized-ami/1.21/amazon-linux-2/recommended/image_id" } data "aws_ssm_parameter" "eks_gpu_ami" { name = "/aws/service/eks/optimized-ami/1.21/amazon-linux-2-gpu/recommended/image_id" } resource "aws_launch_template" "node_template" { name_prefix = "eks_sample_node_group_template" image_id = local.gpu == true ? data.aws_ssm_parameter.eks_gpu_ami.value : data.aws_ssm_parameter.eks_cpu_ami.value instance_type = var.instance_type vpc_security_group_ids = [ aws_security_group.node.id ] key_name = aws_key_pair.key.id block_device_mappings { device_name = "/dev/xvda" ebs { volume_size = var.disk_size } } iam_instance_profile { name = aws_iam_instance_profile.instance_role.name } tag_specifications { resource_type = "instance" tags = { Name = "eks-sample-node" } } user_data = base64encode (local.node_user_data) } # EKS NodeのAutoScaleGroup resource "aws_autoscaling_group" "node_group" { vpc_zone_identifier = [ aws_subnet.outposts_subnet01.id ] desired_capacity = var.desired_node max_size = var.max_node min_size = var.min_node health_check_type = "EC2" wait_for_capacity_timeout = "120m" launch_template { id = aws_launch_template.node_template.id version = "$Latest" } tag { key = "kubernetes.io/cluster/$ { aws_eks_cluster.main.name } " value = "owned" propagate_at_launch = true } } resource "aws_iam_role" "node_group_role" { name = "node-group-role-$ { var.name } " assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF } resource "aws_iam_role_policy_attachment" "eks-worker-node-policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" role = aws_iam_role.node_group_role.name } resource "aws_iam_role_policy_attachment" "eks-cni-policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" role = aws_iam_role.node_group_role.name } resource "aws_iam_role_policy_attachment" "eks-container-registry-read-only" { policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" role = aws_iam_role.node_group_role.name } resource "aws_iam_instance_profile" "instance_role" { name = local.node_role_name role = aws_iam_role.node_group_role.name } # EKS NodeにEIPをAttachするためのIAM Policy resource "aws_iam_policy" "eip_attach_policy" { name = "eip-attach-policy-$ { var.name } " policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:DescribeAddresses", "ec2:AssociateAddress" ], "Resource": "*" } ] } EOF } resource "aws_iam_role_policy_attachment" "eip_attach_policy" { role = aws_iam_role.node_group_role.name policy_arn = aws_iam_policy.eip_attach_policy.arn } # セルフマネージド型ノードがEKSに登録されるための設定、BastionでのAPIサーバに対するkubectl操作の許可設定 resource "local_file" "aws_auth" { content = <<EOF apiVersion: v1 kind: ConfigMap metadata: name: aws-auth namespace: kube-system data: mapRoles: | - rolearn: $ { aws_iam_role.node_group_role.arn } username: system:node:{{EC2PrivateDNSName}} groups: - system:bootstrappers - system:nodes - rolearn: $ { aws_iam_role.bastion.arn } username: bastionrole groups: - system:masters EOF filename = "_aws-auth.yaml" } eks_node.tfでは、EKS Nodeに関するリソースを定義しています。 Outposts上ではAuto Scale Groupの利用が可能なためその設定を定義しています。ここでの特徴はAuto Scale Groupの各ノードに対して user_data の中の処理でCoIPを設定しているところです。Auto Scale Groupには自動でEIPを設定が現状できないため、この設定によりLocal Gateway経由でインターネットやオンプレ環境と通信することが可能となります。 また、 aws_auth ではBastionのRoleがAPIサーバにアクセスするための設定ファイルを作成しています。 security_group.tf resource "aws_security_group" "cluster" { vpc_id = aws_vpc.main.id name = "eks-cluster-security-group-$ { var.name } " tags = { Name = "eks-cluster-security-group-$ { var.name } " } ingress { from_port = 1025 to_port = 65535 protocol = "tcp" security_groups = [ aws_security_group.node.id ] } ingress { from_port = 443 to_port = 443 protocol = "tcp" security_groups = [ aws_security_group.node.id, aws_security_group.bastion.id ] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = [ "0.0.0.0/0" ] } } resource "aws_security_group" "node" { vpc_id = aws_vpc.main.id name = "eks-node-sg-$ { var.name } " tags = { Name = "eks-node-sg-$ { var.name } " } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = [ "0.0.0.0/0" ] } } resource "aws_security_group_rule" "node_ingress_443" { security_group_id = aws_security_group.node.id type = "ingress" from_port = 443 to_port = 443 protocol = "tcp" source_security_group_id = aws_security_group.cluster.id } resource "aws_security_group_rule" "node_ingress_1025_65535" { security_group_id = aws_security_group.node.id type = "ingress" from_port = 1025 to_port = 65535 protocol = "tcp" source_security_group_id = aws_security_group.cluster.id } resource "aws_security_group_rule" "node_ingress_localgateway" { security_group_id = aws_security_group.node.id type = "ingress" from_port = 0 to_port = 0 protocol = "all" cidr_blocks = var.local_cidr_blocks } resource "aws_security_group_rule" "node_ingress_self" { security_group_id = aws_security_group.node.id type = "ingress" from_port = 0 to_port = 0 protocol = "all" self = true } resource "aws_security_group" "vpc_endpoint_ec2" { vpc_id = aws_vpc.main.id name = "vpc-endpoint-ec2-sg-$ { var.name } " tags = { Name = "vpc-endpoint-ec2-sg-$ { var.name } " } ingress { from_port = 0 to_port = 0 protocol = "all" security_groups = [ aws_security_group.node.id ] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = [ "0.0.0.0/0" ] } } resource "aws_security_group" "bastion" { vpc_id = aws_vpc.main.id name = "bastion-sg-$ { var.name } " tags = { Name = "bastion-sg-$ { var.name } " } # オンプレ環境からのみアクセス可能な設定にする ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = var.local_cidr_blocks } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = [ "0.0.0.0/0" ] } } security_group.tfにはSecurity Groupに関するリソースを定義します。 こちらでは、EKSやオンプレ環境とのSecurity Groupについて設定されています。 local_cidr_blocks については適宜それぞれのオンプレ環境に適した設定が必要となります。EKSについての詳細は次の公式ドキュメントを参照してください。 Amazon EKS セキュリティグループの考慮事項 bastion.tf locals { # APIサーバを操作するためのkubectl, helmなどの各種ツールをインストールする bastion_user_data = <<EOF #!/bin/bash set -o xtrace # install kubectl curl --retry 180 --retry-delay 1 -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl # install helm curl -sSL https: //raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash EOF } data "aws_ssm_parameter" "amzn2_ami" { name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" } data "aws_iam_policy_document" "assume_role" { statement { actions = [ "sts:AssumeRole" ] principals { type = "Service" identifiers = [ "ec2.amazonaws.com" ] } } } resource "aws_iam_role" "bastion" { name = "bastion-role-$ { var.name } " assume_role_policy = data.aws_iam_policy_document.assume_role.json } # APIサーバ認証のためのIAM Policy resource "aws_iam_policy" "bastion" { name = "bastion-policy-$ { var.name } " policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "eks:UpdateClusterConfig", "eks:DescribeUpdate", "eks:DescribeCluster" ], "Resource": "$ { aws_eks_cluster.main.arn } " }, { "Effect": "Allow", "Action": [ "eks:ListClusters" ], "Resource": "*" } ] } EOF } resource "aws_iam_role_policy_attachment" "bastion" { role = aws_iam_role.bastion.name policy_arn = aws_iam_policy.bastion.arn } resource "aws_iam_instance_profile" "bastion" { role = aws_iam_role.bastion.name } resource "aws_instance" "bastion" { ami = data.aws_ssm_parameter.amzn2_ami.value subnet_id = aws_subnet.outposts_subnet01.id instance_type = var.bastion_instance_type key_name = aws_key_pair.key.key_name vpc_security_group_ids = [ aws_security_group.bastion.id ] iam_instance_profile = aws_iam_instance_profile.bastion.name user_data = local.bastion_user_data tags = { Name = "bastion-instance-$ { var.name } " } } resource "aws_eip_association" "bastion" { instance_id = aws_instance.bastion.id allocation_id = aws_eip.bastion.id } bastion.tfにはBastion(踏み台)に関するリソースを定義します。 こちらでは、BastionにEKSのAPIサーバを操作可能とするためのIAM Roleを設定していることがわかると思います。また通信もCoIPを設定してオンプレ環境からのみ接続できるように設定します。 output.tf output "cluster" { value = aws_eks_cluster.main.name } output "alb_arn" { value = aws_iam_policy.aws_load_balancer_controller.arn } output "region" { value = var.region } output "ip_pool" { value = local.customer_owned_ipv4_pool } output "vpc_cidr" { value = aws_vpc.main.cidr_block } output "bastion_ip" { value = aws_eip.bastion.customer_owned_ip } output.tfにはk8sアプリケーションのデプロイに使用する値を定義します。 Terraform Apply 上記のtfファイルを次のようにapplyすることで一連のリソースの自動構築が可能です。 terraform init terraform plan # dry run terraform apply Setup EKS 初期には、APIサーバにはEKSクラスタ作成者のみがアクセス可能なため sshuttle というsshでBastion経由の通信を可能とするツールを用います。aws-authをapplyした後はBastion上での操作が可能となります。まず初めに helm を用いてEKS上でALB利用するためのOIDCやServiceAccountなどの設定をして、サンプルアプリケーションgame2048をデプロイしてみます。 事前に使用する sshuttle , kubectl , helm などのツールをインストールします。 動作確認環境: Apple M1 Max MacBookPro18,4 macOS 12.2.1 Darwin 21.3.0 brew install sshuttle brew install kubectl brew install helm brew tap weaveworks/tap brew install weaveworks/tap/eksctl CLUSTER="`terraform output -raw cluster`" ALB_ARN="`terraform output -raw alb_arn`" REGION="`terraform output -raw region`" IP_POOL="`terraform output -raw ip_pool`" BASTION_IP="`terraform output -raw bastion_ip`" VPC_CIDR="`terraform output -raw vpc_cidr`" sshuttle -r ec2-user@$BASTION_IP $VPC_CIDR & PID=$! aws eks --region $REGION update-kubeconfig --name $CLUSTER kubectl apply -f _aws-auth.yaml # oidc eksctl utils associate-iam-oidc-provider \ --region=ap-northeast-1 \ --cluster=$CLUSTER \ --region=$REGION \ --approve # attach policy eksctl create iamserviceaccount \ --cluster=$CLUSTER \ --namespace=kube-system \ --name=aws-load-balancer-controller \ --attach-policy-arn=$ALB_ARN \ --override-existing-serviceaccounts \ --approve # ssh bastion ssh ec2-user@$BASTION_IP # ALB helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ -n kube-system \ --set clusterName=$CLUSTER \ --set serviceAccount.create=false \ --set image.repository=602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/amazon/aws-load-balancer-controller \ --set serviceAccount.name=aws-load-balancer-controller # ALB Application game2048 curl -s https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.3.1/docs/examples/2048/2048_full.yaml | \ sed "51s|.*| alb.ingress.kubernetes.io/customer-owned-ipv4-pool: $IP_POOL|" > _2048_full.yaml kubectl apply -f _2048_full.yaml kill -9 $PID ingressの設定の中に alb.ingress.kubernetes.io/customer-owned-ipv4-pool 3 を設定することでオンプレからアクセス可能なALBを作成することが可能となります。 上記のようにgame-2048をdeployすることで以下のようにingressが立ち上がります。表示されたURIにオンプレ環境からアクセスすることでサンプルのアプリケーションが表示されます。 kubectl get ingress -n game-2048 NAME CLASS HOSTS ADDRESS PORTS AGE ingress-2048 <none> * k8s-game2048-ingress2-xxxxxxxxx-1234567890.ap-northeast-1.elb.amazonaws.com 80 4d10h 以上のようにサンプルアプリケーションを実行できました。 まとめ 本記事では、Terraformを用いてOutposts上でオンプレ環境からのみ管理・アクセス可能なPrivate EKSを構築する方法を紹介しました。 開発面では、オンプレ環境でもIaCを用いてマネージドk8sを利用することが大きなメリットかと思います。利用面でもセキュアな可用性の高いアプリケーションを構築することが可能となるため非常に有用であると思います。 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet ↩ https://docs.aws.amazon.com/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html ↩ https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/guide/ingress/annotations/#customer-owned-ipv4-pool ↩
アバター
はじめに こんにちは、イノベーションセンターの福田です。 前回 に引き続き、 AWS Outposts について紹介していきます。 今回は、特に AWS CDK による AWS Outposts の Infrastructure as Code (以降 IaC) を行う方法について記載していきたいと思います。 AWS CloudFormation AWS CDK では AWS CloudFormation というサービスをバックエンドで利用しているため、まずはこの AWS CloudFormation について解説します。 AWS CloudFormation は、AWS の各種リソースを JSON/YAML 形式で記述した"テンプレート"として管理します。 これによって AWS リソースの IaC を実現してくれますが、サービスとして IaC を実現するものというよりは AWS リソースの管理を AWS がマネージしてくれるものといった方が近いです。 料金はデプロイされた各種リソースにかかる料金のみで、 CloudFormation の利用自体に料金はかかりません。 CloudFormation では自動で依存関係等を解決し、適切な形でリソースのライフサイクルを管理してくれます。 デプロイされたリソース群は スタック という単位で管理されます。 このスタックへ、リソースを新しく追加したり、削除したりしても CloudFormation が依存するものの管理をおこなってくれます。 これによってユーザ側で Web コンソールや AWS CLI 等で頑張ってリソースの状態等を管理することから解放されます。 さらに、デプロイに失敗しても以前に成功してくれた状態にロールバックしてくれるため、自前で AWS のリソースのデプロイに失敗したから自分で再度構築しなおすということも行う必要はありません。 AWS CDK AWS CDK とは AWS Cloud Development Kit の略です 1 。 AWS CDK 自体は一般的なプログラミング言語(TypeScript, Python, Java 等)で AWS リソースを定義・管理できるオープンソースのフレームワークです。 最終的には AWS CloudFormation のテンプレートに直り、そのテンプレートを通して各種リソースを AWS CloudFormation へプロビジョニングします。 AWS CloudFormation では JSON/YAML でテンプレートを記載していく関係上、定義が長くなりがちです。 一方、 AWS CDK はテンプレートを汎用のプログラミング言語で定義できるため、非常に短いコードで CloudFormation テンプレートを定義できます。 また、プログラミング言語を使って動的に設定できる値を自動で解決してくれるため、全てを自分で書いて定義しなければならない CloudFormation よりもコードを短くできます。 AWS CDK では各種リソースを コンストラクター というもので定義します。 コンストラクターとは要は各種リソースに対応した CDK 上のコンポーネントになります。 このコンストラクターは主に2つに分けられます 2 。 ローレベルコンストラクター CloudFormation のテンプレートにおけるリソースと一対一対応したコンストラクターです。 例えば aws-cdk-lib/aws-ec2 の CfnVpc コンストラクターは CloudFormation 上の AWS::EC2::VPC リソースにそのまま対応します ハイレベルコンストラクター ローレベルコンストラクターを利用しやすくラップしたり、各種リソース配下で利用するリソース(たとえば VPC における subnet 等)も一緒に簡単な設定値を指定すると自動でそれに必要なリソースも一緒に定義してくれたりしてくれるより抽象度の高いコンストラクターです。 例えば aws-cdk-lib/aws-ec2 の Vpc コンストラクターは subnetConfiguration というプロパティに subnet の設定を入れると subnet の定義や、そこに展開される NAT の定義等も個別に定義せずともまとめて行ってくれます。 ローレベルコンストラクターありきなので、ローレベルコンストラクターがある機能に対応してなければハイレベルコンストラクターも対応していません。 基本的にはハイレベルコンストラクターを利用し、ハイレベルコンストラクターで対応できない事情があればローレベルコンストラクターを利用します 3 。 現在、 v1 と v2 がそれぞれ存在している状態なのですが、ここでは v2 ベースにして解説していきます。 カスタムリソース 現在、 AWS Outposts 上に展開する一部リソースは CloudFormation では未対応な部分があるため、 CloudFormation の カスタムリソース という機能を利用する必要があります。 ここでは本題へ入る前に、今回使用するカスタムリソースを解説します。 カスタムリソースとは CloudFormation におけるリソースのカスタムプロビジョニングロジックを定義する方法です。 例えば、 CloudFormation が対応してないリソースを CloudFormation テンプレートで管理したい CloudFormation が対応してない設定をリソースに定義したい といったような場合に利用し、 CloudFormation で対応してないリソースでもテンプレート上で管理できるようになります。 実はカスタムリソースの実体は Lambda の関数です。 定義しておいた Lambda の関数をスタックのプロビジョニング時、つまりスタック上の他のリソースがプロビジョニングされているときに呼ぶことでリソースを作成/更新/修正する AWS API を呼び出し、リソースのプロビジョニングを行う仕組みです。 これによって、 AWS API が提供されているものの CloudFormation に対応してない新サービスのリソースをプロビジョニングすることを実現しています。 AWS CDK でもこの CloudFormation のカスタムリソースに対応しています。 特に、 AWS CDK ではカスタムリソースを利用しやすくする Provider Framework があるため、簡単にカスタムリソースを提供できます。 AWS CDK のリファレンスではいくつかの カスタムリソース実装例 が提供されていますので、詳細はそちらをご確認ください。 ここではカスタムリソースを定義する Provider Framework について少し解説しておきます。 カスタムリソースを利用するには、まずリソースの管理する Lambda 関数を定義します。 この際、 AWS API を呼び出す場合は、その API と操作対象のリソースへの操作許可ポリシーを定義しておく必要があります。 これは Function コンストラクター の initialPolicy プロパティー で定義します。 import { Stack , StackProps } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; import * as lambda from 'aws-cdk-lib/aws-lambda' ; import * as iam from 'aws-cdk-lib/aws-iam' ; export class Stack extends cdk.Stack { constructor( scope: Construct , id: string , props?: StackProps ) { // ... const handler = new lambda. Function ( this , "Handler" , { // ... initialPolicy: [ new iam.PolicyStatement ( { resources: [ "*" ] , actions: [ // ここに Lambda で扱うリソースを操作する API に対応したアクションを追加する。 ] , } ), ] , } ); } } ハンドラーを定義したあとは次のようなボイラープレートコードを生成しておけばカスタムリソースの定義は完了です。 import { Stack , StackProps } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; import * as lambda from 'aws-cdk-lib/aws-lambda' ; import * as iam from 'aws-cdk-lib/aws-iam' ; import * as cr from 'aws-cdk-lib/custom-resources' ; import * as cdk from 'aws-cdk-lib' ; export class Stack extends cdk.Stack { constructor( scope: Construct , id: string , props?: StackProps ) { // ... const handler = new lambda. Function ( this , "Handler" , { // ... } ); const provider = new cr.Provider ( this , "Provider" , { onEgventHandler: handler , } ); const resource = new cdk.CustomResource ( this , "Resource" , { serviceToken: provider.serviceToken , } ); } } 次に、カスタムリソースのライフサイクルを管理する Lambda 関数を作成していきます。 Lambda 関数は作成/更新/削除時に呼ばれますが、これらのイベントの種別は Lambda 関数の引数に RequestType というプロパティとしてわたってきます。 なので、 Lambda 関数を次のような形で記載してあげるとリソースを管理できます 4 。 import * as lambda from 'aws-lambda' ; const createHandler = async ( event: lambda.CloudFormationCustomResourceCreateEvent ) : Promise < lambda.CloudFormationCustomResourceResponse > => { // ここは後で実装します } const updateHandler = async ( event: lambda.CloudFormationCustomResourceUpdateEvent ) : Promise < lambda.CloudFormationCustomResourceResponse > => { // ここは後で実装します } const deleteHandler = async ( event: lambda.CloudFormationCustomResourceDeleteEvent ) : Promise < lambda.CloudFormationCustomResourceResponse > => { // ここは後で実装します } export const handler = async ( event: lambda.CloudFormationCustomResourceEvent ) : Promise < lambda.CloudFormationCustomResourceResponse > => { switch ( event.RequestType ) { case 'Create' : return createHandler ( event ); case 'Update' : return updateHandler ( event ); case 'Delete' : return deleteHandler ( event ); default : throw new Error ( "unreachable" ); } } このように定義した Lambda 関数から リンク先 のようなレスポンスを返すと、 CloudFormation がその結果を利用してどのようなリソースを管理しているのかを把握します。 各種レスポンス値の意味は次の通りです。 Status : リソースの作成に成功したかを表すステータスです。ここは SUCCESS を固定で指定します。 LogicalReousrceId : CloudFormation の Logical Resource ID を指定します。これは event.LogicalResourceId として渡ってくるので、これをそのまま指定します PhysicalResourceId : 実際に作成されたリソースにつけられる ID や ARN 等、そのリソースを一意に表現する値を指定します(基本的にはリソースの ID か ARN を指定することになります) RequestId : Lambda の実行がリクエストされた際に付与される ID です。これは event.RequestId として既に渡ってきているので、これをそのまま指定します StackId : デプロイを管理しているスタックの ID を指定します。これは event.StackId として既に渡ってきているので、これをそのまま指定します 各種ステータスや作成したいリソースの設定値等は event.ResourceProperties というプロパティに詰めらています。 import * as lambda from 'aws-lambda' ; /** * Lambda 実行時に例外が出だときに CloudFormation 側でエラーを補足してスタックの * ロールバックを行ってくれます。 * * 実はドキュメントだとエラーがおこった際は FAILED を Status に設定したレスポンスを * 返すよう指示がされていますが、これを丁寧に守って返すとエラーが発生しても CloudFormation 側は * エラーがおこったことを無視してデプロイに成功したとします。 * * ref: https://docs.aws.amazon.com/cdk/api/v1/docs/custom-resources-readme.html#handling-lifecycle-events-onevent */ const createHandler = async ( event: lambda.CloudFormationCustomResourceCreateEvent ) : Promise < lambda.CloudFormationCustomResourceResponse > => { // ResourceProperties にリソースの設定値が辞書形式で格納される const properties = event.ResourceProperties ; // ここで AWS API を呼び出す等してリソースを作成する。 return { Status: "SUCCESS" , LogicalResourceId: event.LogicalResourceId , PhysicalResourceId: "<ここには作成したリソースに付くユニークな ID や ARN を指定する>" , RequestId: event.RequestId , StackId: event.StackId , } ; } const updateHandler = async ( event: lambda.CloudFormationCustomResourceUpdateEvent ) : Promise < lambda.CloudFormationCustomResourceResponse > => { // ResourceProperties にリソースの設定値が辞書形式で格納される。 // もし、 Update が失敗した場合は以前の設定値が ResourceProperties に保存してくるので、これを元に既存のリソースをつくりなおすことになる。 const properties = event.ResourceProperties ; // ここでリソースを作り直す return { Status: "SUCCESS" , LogicalResourceId: event.LogicalResourceId , // ドキュメントによると新しい Physical Resource ID を指定すると // 更に Delete イベントが走ってリソースの削除を行おうとする。 // // ref: https://docs.aws.amazon.com/cdk/api/v1/docs/custom-resources-readme.html#handling-lifecycle-events-onevent PhysicalResourceId: "<作り直したリソースに付いている ID や ARN>" , RequestId: event.RequestId , StackId: event.StackId , } ; } const deleteHandler = async ( event: lambda.CloudFormationCustomResourceDeleteEvent ) : Promise < lambda.CloudFormationCustomResourceResponse > => { const properties = event.ResourceProperties ; // ここでリソースを削除する return { Status: "SUCCESS" , LogicalResourceId: event.LogicalResourceId , PhysicalResourceId: event.PhysicalResourceId , RequestId: event.RequestId , StackId: event.StackId , } ; } もし付加的な情報を返す場合、 lambda.CloudFormationCustomResourceResponse に Data というプロパティを利用します。 Data に辞書形式で値とキーを指定しておけば、後で CloudFormation にある Fn::GetAttr 関数 5 へキーを指定することで Data に指定したキーを持つ値を参照できます。 たとえば subnet を作成した上でその ID を後で参照しようとしたとき、まずは Lambda から Data プロパティを介して値を参照できるようにします(ここでは SubnetId というキーとします)。 import * as lambda from 'aws-lambda' ; const createHandler = async ( event: lambda.CloudFormationCustomResourceCreateEvent ) : Promise < lambda.CloudFormationCustomResourceResponse > => { // ResourceProperties にリソースの設定値が辞書形式で格納される const properties = event.ResourceProperties ; // ここで AWS API を呼び出す等してリソースを作成する。 return { Status: "SUCCESS" , LogicalResourceId: event.LogicalResourceId , PhysicalResourceId: "<ここには作成したリソースに付くユニークな ID や ARN を指定する>" , RequestId: event.RequestId , StackId: event.StackId , Data: { SubnetId: "作成された subnet の ID" , } } ; } これを CDK 側で参照する場合は次のようにします。 import { Stack , StackProps } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; import * as cdk from 'aws-cdk-lib' ; export class Stack extends cdk.Stack { constructor( scope: Construct , id: string , props?: StackProps ) { // ... const resoure = new cdk.CustomResource ( this , "CustomResource" , { /* ... */ } ); const subnetId = resource.getAtt ( "SubnetId" ); // Data に登録された SubnetId の値を取得 } } ただし、 getAtt は AWS CDK 上だと文字列ではく aws-cdk-lib の IResolvable 型として返ってきます。 返ってきた値を文字列として参照したい場合は次のように aws-cdk-lib の token.asString メソッドを噛まして変換をしておく必要があります 6 。 import { Stack , StackProps } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; import * as cdk from 'aws-cdk-lib' ; export class Stack extends cdk.Stack { constructor( scope: Construct , id: string , props?: StackProps ) { // ... const resource = new cdk.CustomResource ( this , "CustomResource" , { /* ... */ } ); const subnetId = cdk.Token.asString ( resource.getAtt ( "SubnetId" )); } } 一方で、ライフサイクルを管理する Lambda 関数を呼び出すとき、引数を渡したい場合があると思います。 たとえば、 ALB を作成/更新/削除するカスタムリソースを作成したとします。 すると、 CreateLoadBalancer API には ALB につける名前を定義する必要がありますが、これを動的に CDK で管理したい場合などが該当します。 このような場合、 aws-cdk-lib の CustomResource にある properties というプロパティを使用します。 具体的には、次のように properties へ辞書形式で設定値を Lambda 関数へ流し込むことになります。 import { Stack , StackProps } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; import * as lambda from 'aws-cdk-lib/aws-lambda' ; import * as iam from 'aws-cdk-lib/aws-iam' ; import * as cr from 'aws-cdk-lib/custom-resources' ; import * as cdk from 'aws-cdk-lib' ; export class Stack extends cdk.Stack { constructor( scope: Construct , id: string , props?: StackProps ) { // ... const name = "<ALB の名前>" ; const handler = new lambda. Function ( this , "Handler" , { // ... } ); const provider = new cr.Provider ( this , "Provider" , { onEgventHandler: handler , } ); const resource = new cdk.CustomResource ( this , "Resource" , { serviceToken: provider.serviceToken , // properties に辞書形式で引数を登録します。 // ここでは例として、 subnet id を `subnetId` という引数名で渡しています。 properties: { name: name , } , } ); } } すると、 Lambda 関数では次のように properties の値を参照できます。 import * as lambda from 'aws-lambda' ; export const handler = async ( event: lambda.CloudFormationCustomResourceEvent ) : Promise < lambda.CloudFormationCustomResourceResponse > => { // event に ResourceProperties というプロパティがあり、ここに // cdk.CustomResource の properties に指定した値が格納される。 const { name } = event.ResourceProperties ; // ここで properties で指定した name を取り出しています。 } 以上で、カスタムリソースのライフサイクルを管理する Lambda 関数が定義できました。 ここまででカスタムリソースを作成する一連の方法について解説していきました。 詳細なライフサイクル管理について知りたいかたは Provider Framework のドキュメントをご覧ください。 特に、 Update や Delete, Create 時に失敗したときにこちらがどのように対応すべきかについては Important cases to handle にまとまっていますので、一読しておくことをおすすめします。 Outposts IaC with AWS CDK ここまでで読み進めるにあたって必要な準備が整いましたので、さっそく本題に入ります。 今回は AWS CDK を使って Outposts に展開するリソースを定義する方法について私達が試した範囲で記載していきます。 subnet subnet 自体をデプロイするのは簡単です。 既に CloudFormation で展開したい Outpost 筐体の ARN を指定すればデプロイできるようになっています。 ただし、ローレベルコンストラクターのみ Outpost ARN の設定に対応しています。 ハイレベルコンストラクターからどの Outpost 筐体へデプロイするかの設定はできませんが、どの Outpost 筐体にデプロイされたのかを subnetOutpostArn から取得することのみはサポートされています 7 。 import { Stack , StackProps } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; import * as ec2 from 'aws-cdk-lib/aws-ec2' ; export class Stack extends cdk.Stack { constructor( scope: Construct , id: string , props?: StackProps ) { // ... new ec2.CfnSubnet ( this , "Subnet" , { // ... outpostArn: "arn:aws:outposts:<デプロイ先 Outpost 筐体がデプロイされているリージョン>:<デプロイ先 Outpost 筐体を所持するAWS アカウント ID>:outpost/<デプロイ先 Outpost 筐体の Outposts ID>" , // outpostARN を指定する場合、対象 outpost 筐体が所属している AZ も // 指定しておかなければならない availabilityZone: "<デプロイ先 Outpost が所属している AZ>" , } ); } } ちなみに、 CloudFormation で subnet へ展開するリソースに public IP を自動で付与する設定 8 はありますが、 customer owned IP address 9 に対応するそのような設定は現在 CloudFormation ではサポートされていません。 これが設定できないと、例えば ALB や NAT をデプロイした際に customer owned IP address を自動でつけてくれないので、オンプレミス側から NAT や ALB を参照できません 10 。 現状、この設定をするにはカスタムリソースを使って AWS API を呼び出して設定しなければなりませんので、ここでカスタムリソースを使う必要があります。 実際には ModifySubnetAttribute を呼び出せば目的は達成できるため、前述の Lambda 関数中で対応する AWS API を作成/更新時に呼び出せば OK です。 サンプルコードは こちら に用意したので、参考にしてください。 EBS Volume EC2 インスタンスデプロイ時にくっつくルートボリュームを担う EBS Volume は、 EC2 インスタンスを Outpost 筐体上の subnet に配置することで自動的にその subnet と同じ Outpost 筐体上にデプロイされます。 一方、自分で追加の EBS Volume をつくる場合はローレベルコンストラクターである CfnVolume コンストラクター にデプロイ先 Outpost 筐体の ARN を指定すれば OK です。 import { Stack , StackProps } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; import * as ec2 from 'aws-cdk-lib/aws-ec2' ; export class Stack extends cdk.Stack { constructor( scope: Construct , id: string , props?: StackProps ) { // ... new ec2.CfnVolume ( this , "Volume" , { // ... outpostArn: "arn:aws:outposts:<デプロイ先 Outpost 筐体がデプロイされているリージョン>:<デプロイ先 Outpost 筐体を所持するAWS アカウント ID>:outpost/<デプロイ先 Outpost 筐体の Outposts ID>" , } ); } } ただし、注意点として、 EC2 インスタンスに対応するハイレベルコンストラクターである Instance コンストラクター 内の EBS Volume を定義するためのプロパティである blockDevices では、 Outposts ARN を指定できません 11 。 したがって Outpost 筐体上に展開する追加の EBS Volume を EC2 インスタンスへアタッチする場合は、 ローレベルである CfnVolume を使って Outpost 筐体上にデプロイするように設定し、 CfnVolumeAttachment でアタッチする そもそもローレベルコンストラクターでインスタンスも定義する の2択になります。 次のコードは 1. の選択肢をとった場合の例です。 import { Stack , StackProps } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; import * as ec2 from 'aws-cdk-lib/aws-ec2' ; export class Stack extends cdk.Stack { constructor( scope: Construct , id: string , props?: StackProps ) { // ... // インスタンスを用意 const instance = new ec2.Instance ( this , "Instance" , { /* ... */ } ); // 追加の EBS Volume を用意 const additionalVolume = new ec2.CfnVolume ( this , "AdditionalVolume" , { // ... outpostArn: "arn:aws:outposts:<デプロイ先 Outpost 筐体がデプロイされているリージョン>:<デプロイ先 Outpost 筐体を所持するAWS アカウント ID>:outpost/<デプロイ先 Outpost 筐体の Outposts ID>" , } ); //EBS Volume をアタッチ new ec2.CfnVolumeAttachment ( this , "AdditionalVolumeAttachment" , { device: '<アタッチ先デバイス名>' , volumeId: additionalVolume.ref , instanceId: instance.instanceId , } ); } } ALB ALB の場合は customer owned IP address を付与しない場合はハイレベルコンストラクターでも設置先 subnet を指定できる 12 ので、 Outposts 筐体上に展開しているサブネットへデプロイすれば Outpost 筐体上へ自動でデプロイされます。 しかし、 customer owned IP address を付与しようとした ALB は現在 CloudFormation できません 13 。 なので、 customer owned IP address を ALB へ付与する場合はロードバランサーの作成段階からカスタムリソースを使う必要があります 14 。 実際のコードは リポジトリー に 例 を載せたのでそちらを確認してください。 基本的にはロードバランサーを作成/削除してるだけです。 S3 on Outposts S3 on Outposts はローレベルなもののみ提供されています 15 。 ちなみに、 CDK で提供されているモジュールはリファレンスを参照するとまだプレビュー段階であることが伺えます。 まだ安定はしてないようなので、利用する際には注意が必要です。 The construct library for this service is in preview. Since it is not stable yet, it is distributed as a separate package so that you can pin its version independently of the rest of the CDK. https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3outposts-readme.html 前回も書きましたが、 S3 on Outposts を利用するためには次の2つを必要とします。 Outposts エンドポイント S3 アクセスポイント これらは VPC 上へ設置することになるため、 VPC からのみ S3 on Outposts バケットへアクセスできます。 したがって、 S3 on Outposts を利用できるようにするにはバケットを用意しつつ、この2つを VPC 上に設置すれば OK です。 S3 アクセスポイントは 接続先バケットの ARN デプロイ先 VPC の VPC ID アクセスポイント名 の設定がデプロイに最低限必要です。 Outposts エンドポイントは デプロイ先 Outpost 筐体についている Outposts ID エンドポイントにつける セキュリティグループ ID デプロイ先サブネットの ID の3つがデプロイに必要です。 ちなみに、もしデプロイする場合、 CfnEndpoint のドキュメントにもありますが、エンドポイントの作成に時間がかかるので注意してください。 It can take up to 5 minutes for this resource to be created. https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3outposts.CfnEndpoint.html これらをデプロイするコード例は次のようになります。 import { Stack , StackProps } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; import * as ec2 from 'aws-cdk-lib/aws-ec2' ; import * as s3outposts from 'aws-cdk-lib/aws-s3outposts' ; export class Stack extends cdk.Stack { constructor( scope: Construct , id: string , props?: StackProps ) { // ... // S3 アクセスポイントは VPC にデプロイされるので、 S3 アクセスポイントを設置したい VPC を定義 const vpc = new ec2.Vpc ( this , "Vpc" , { /* ... */ } ); // Outposts エンドポイントはサブネットへ紐付くため、 Outposts エンドポイントを設置したい サブネットを定義 const subnet = new ec2.Subnet ( this , "Subnet" , { // ... vpcId: vpc.vpcId , outpostArn: "arn:aws:outposts:<デプロイ先 Outpost 筐体がデプロイされているリージョン>:<デプロイ先 Outpost 筐体を所持するAWS アカウント ID>:outpost/<デプロイ先 Outpost 筐体の Outposts ID>" , } ); // S3 on Outposts バケットを用意する const bucket = new s3outposts.CfnBucket ( this , "Bucket" , { bucketName: "<デプロイしたいバケット名>" , outpostId: "<デプロイ先 Outpost 筐体の Outposts ID>" , } ); // S3 アクセスポイントを用意する new s3outposts.CfnAccessPoint ( this , "AccessPoint" , { bucket: bucket.ref , // このアクセスポイントで接続する S3 on Outposts のバケット ARN を指定する。ここでは先にバケットを定義しており、そこからできる ARN を参照するようにする。 // デプロイ先の VPC の設定をする vpcConfiguration: { vpcId: vpc.vpcId , // 現状、デプロイ先 VPC の ID しか設定するプロパティが用意されてない } , name: "<アクセスポイントに設定したいアクセスポイント名>" , } ); // Outposts エンドポイントは Security Group を要求してくるので用意する const endpointSecurityGroup = new ec2.SecurityGroup ( this , "EndpointSecurityGroup" , { /* ... */ } ); // Outposts エンドポイントを用意する new s3outposts.CfnEndpoint ( this , "Endpoint" , { outpostId: "<デプロイ先 Outpost 筐体の Outposts ID>" , securityGroupId: endpointSecurityGroup.securityGroupId , subnetId: subnet.ref , } ); } } まとめ 今回は AWS CDK で Outposts を IaC する方法の紹介をしてきました。 一部サービスはまだ多少こちらでカスタムリソースを使って管理しなければならないことが見てとれると思います。 カスタムリソースを利用するには多少 AWS API を利用して AWS リソースを作成することに習熟しておく必要があり、また AWS CDK でカスタムリソースを用意する知識が必要なため、ハードルは高めです。 そのため、現状では AWS CDK で Outposts をやるのはまだ難しいところがあります。 しかし、少しずつ改善はされ、以前はできなかったことができるようになってきたりなどしているので、今後も状況の改善がなされていくことが期待できます。 https://aws.amazon.com/jp/cdk/ ↩ これ以外にも patterns というものがあります。こちらはハイレベルコンストラクターのものをまとめて、一般的に利用されるシーンのものに合わせてさらに抽象度を高めています。例としては定期的に実行する Fargate Task を定義する ScheduledFargateTask という pattern が提供されています。 ↩ AWS の新サービスだとローレベルコンストラクターが提供されているがハイレベルコンストラクターは提供されてないという場合があったり、ハイレベルなものが提供されているものの、設定したいものがハイレベルだと設定できないということがあります。そういう場合にローレベルコンストラクターを使います(実はハイレベルコンストラクターのプロパティには node というプロパティが生えており、そこから実際にバックで利用しているローレベルコンストラクターにアクセスできるのでそちらから一部設定値をいじることができたりします)。 ↩ ここでは @types/aws-lambda パッケージを利用して型付けしています ↩ CDK だと aws-cdk-lib/custom-resources の CustomResource コンポーネントに生えてる getAtt メソッドが対応する関数になります。 ↩ 他にも数値型として参照できたりします。詳しくは ドキュメント をご覧ください。 ↩ https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.Subnet.html#subnetoutpostarn ↩ AWS::EC2::Subnet の MapPublicIpOnLaunch ↩ customer owned IP address については前回の記事をご覧ください。 ↩ 後で設定はできるので、デプロイ後に自分で設定するといったことはできるのですが、ここではこれも自動管理する話をしています。 ↩ blockDevices の型 ( aws-cdk-lib/aws-ec2 の BlockDevice インターフェース ) を辿っても Outpost ARN を指定するためのプロパティが表れないため、指定できないということがわかります。 ↩ https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationLoadBalancer.html#vpcsubnets ↩ https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-loadbalancer.html ↩ subnet と同じように ModifyLoadBalancerAttributes で customer owned IP address を設定できるか確認したのですが、現状では設定できないようなので、そもそも作成段階からカスタムリソースで管理する必要があります。 ↩ https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3outposts-readme.html ↩
アバター
はじめに こんにちは、イノベーションセンターの福田・鈴ヶ嶺です。 普段はクラウドサービスをオンプレ環境でも同様のUI/UXで使用を可能とするハイブリッドクラウド製品の技術検証をしています。 本記事は、今回日本で初めて導入した AWS Outposts ラックの仕様、導入方法、利用方法について徹底解説します。次の画像は、実際に導入した AWS Outposts ラックの画像です。 NTT Com では「Node-AI on AWS Outposts」に関するニュースリリースを2022年3月14日に発表いたしました。 今後は「Node-AI on AWS Outposts」を軸に、自社の多様なサービスやソリューションと組み合わせることで、AWSをご利用されるお客さまのデジタルトランスフォーメーションを支援していきます。 国内初、「AWS Outposts」に自社データ分析ツールを組み込んだソリューションを開発 AWS Outposts ラックとは AWS Outposts ラックは AWS が出しているハイブリッドクラウド AWS Outposts ファミリーの一部であり、42Uの専用ラックをデータセンターなどのオンプレ環境に設置して使用します。 以降は簡易に Outposts と記述します。 Outposts は、 AWS のサービスを オンプレミス上で Public AWS と同じ体験で 実現してくれるものです。 実際、各種 API も展開先の Outposts ARN を指定したりするプロパティが追加される等、 Outposts で利用したいリソースと Public 上の AWS で利用したいリソースとの間で API が分離されているということは(一部を除いて)ありません 1 。 つまり、慣れた AWS API の中でデプロイ先 Outpost 筐体を指定すれば Outpost 筐体上でリソースが展開されるようになっています。 ただし、AWS Outposts 上で展開されるサービスは AWS のフルセットではなく、サブセットになります。 具体的には、 EC2, S3, EKS などの AWS サービスが実行可能です 2 。 Outposts は提供されるハードウェアも含めて AWS によるフルマネージドな管理がなされます。 これによって従来のオンプレシステムに比べて管理稼働の削減が期待されます。 例えば、Outposts 筐体のハードウェアが故障した際には AWS がハードウェア故障の調査をしてくれます。 ユースケース Outposts の主なユースケースは次の通りです。 Public 上の AWS だとネットワークのレイテンシーが大きくなって要件に合わないような環境で、近接環境に AWS Outposts をデプロイすることで、レイテンシーを低減しながら AWS サービスを利用する データレジリエンシーの観点からクラウドにデータを流せないような地域でも AWS Outposts をデプロイすることでデータを特定の地域に留めつつ AWS サービスを利用する 外出しできないようなデータがあるような場所に AWS Outposts をデプロイすることでデータを中に留めつつ AWS サービスを利用する オンプレ環境に Outposts をデプロイし、一部サービスをそちらに移行することで段階的にクラウド化を実施する Outposts を使えばこれらの要件を満たしつつ、クラウドサービスの恩恵を受けることができます。 例えば、次のような環境の場合、 Public 上の AWS を利用するには Public なインターネットを通したり、 AWS の提供する網内を通るためにクラウドサービスを利用できませんでした。 セキュリティ保護要件上データはオンプレ上に保管 オンプレ環境からのみデータへのアクセスを可能にしなければならない AWS Outposts を利用すれば、このような環境でもオンプレ内にデータを留めたまま EC2 インスタンスでそのデータを処理するといったことを実現できます。 AWS Outposts の立ち位置 運用面ではデプロイ先リージョン内の AZ へ紐付く形になります。 例えば ap-northeast-1 リージョンにデプロイし、 ap-northeast-1a に紐付けるという形で運用します。 形としてはある AZ が AWS Outposts によって拡張されるようなイメージです。 したがって、 Outpost 筐体を利用する場合はリージョンだけでなく AZ のレベルを意識しておく必要があります。 データの暗号化 Nitro システムを搭載しているため、 Outpost 筐体内のデータは全て暗号化されています。 Public AWS との接続にも Service Link を介して接続 されており、転送時にもしっかり暗号化されます。 AWS Outposts には Nitro Security Key という機器が設置されており、その機器がデータの暗号化を担当しています。 復元もこの機器が担当しているため、この機器を破壊すると内部のデータの復元は困難となります 3 。 Outposts 導入要件 Outposts はここまで述べたように、従来 Public クラウドで利用が難しかった局面でもクラウドサービスを利用できるようにしてくれるサービスです。 そんな夢のようなサービスですが、 Outposts 導入には幾つかの要件が存在します。 AWS Direct Connect を用いた AWS リージョンの接続 専用線を用意するには準備に多くの稼働や時間が必要となります 接続するネットワークの帯域も AWS から指定されています ラック、ネットワークに関する責任 Outposts ラックの物理的なセキュリティの責任を負います Outposts へのネットワーク接続の一貫性の責任を負います 今回、私達が実際の構築にあたった際には社内で提供しているサービスを利用して構築しました。 これによって要件を満たしつつ、迅速に AWS Outposts を利用することが可能となりました。 NTT ComによるOutpost導入方法 AWS Direct Connect を用いた AWS リージョンの接続 Outposts は AWS リソースとの連携を可能とするために AWS リージョンとの接続が必須となります。 この接続は、 Service Link と呼ばれ AWS Direct Connect を利用した専用接続が必要になります。 データ伝送速度は 1Gbps 以上が推奨されています。 通常 AWS Direct Connect などの専用線を用意するには多くの稼働や NW 機器などのハードウェアの準備が必要となりますが、 NTT Com の次世代インターコネクトサービス Flexible InterConnect(FIC) の FIC-Connection AWS を用いることで迅速に安定した接続を可能とします。 また、 FIC は最大 10Gbps の広帯域接続にも対応しているため高品質なネットワークを提供できます。 次の画像はFICのポータルです。このように専用の物理的な拠点とAWSの閉域接続をポータルで設定・管理することを可能とします。 ラック、ネットワークに関する責任 Outposts の物理的なラックの安全性は利用者側で責任を負います。 導入には重量・電力制限などの様々な設備に対応する必要があります。 これらの課題に対して NTT Com のデータセンターサービス Nexcenter を利用することで対応できました。 Nextcenter では、グローバル統一の「設備基準」や「サービス運用基準」に準拠することで高品質なサービスを提供できます。 また、ネットワークに関してはデータセンター内接続、 FIC による AWS リージョン接続などのそれぞれの接続を冗長設計することにより安定した運用が実現可能です。 オンプレミスネットワークとの統合 オンプレミスネットワークとは Local Gateway を通して接続されます。 この Local Gateway へ与えられたルートテーブルに VPC を関連付けることで、その VPC 内のリソースとオンプレミスネットワークが通信可能な状態になります 4 。 ただし、これだけでは各種 Outposts 内の AWS リソースへ通信はきません。 こちら側から IP アドレスプールを提供し、 Outposts 内の各種 AWS リソースに適切にその IP アドレスプール内にある IP アドレス 5 を付与する必要があります 6 。 例えば、 EC2 インスタンスであれば ENI にその IP アドレスプールから IP アドレスを付与し、その ENI をアタッチしなければオンプレミスネットワークと接続はできません。 もし VPC と Local Gateway の関連付けを行わなかった場合、例え与えた IP アドレスプールから ENI ヘ IP アドレスを払出していてもオンプレミスネットワークと通信できません。 これは Local Gateway のルートテーブルのみがオンプレミスネットワークの内容を知っていて、 AWS の提供する VPC 側は AWS Outposts の存在を知らないからです。 接続したければ、こちらから接続先を明示してあげる必要があるというわけです。 提供されるサービス Outposts 上では様々なサービスを展開できますが、そのうち私達が試した Outposts 上で使用できるサービスについて解説します。 VPC と subnet VPC は Local Gateway のルートテーブルを紐付ければ良いだけで他に設定は必要ありません。 一方、 subnet の方だけ Outposts 上で利用する設定が必要です。 subnet を作成する CreateSubnet API を見ればわかりますが、デプロイ時に Outpost ARN を指定することでその Outpost ARN を持つ Outpost 筐体上にデプロイされます。 注意点として、マネージドコンソール上から作成するには Outposts コンソールから行う必要があります。 現在の VPC コンソール上における subnet の作成画面には Outpost ARN を直接指定するインターフェースがないため、 VPC コンソールからは AWS Outposts 上に subnet を作成できません。 必ず AWS Outposts コンソール上から作成する必要があります。 使用感は Public 上の AWS へデプロイする普通の subnet と変わりません。 管理も AWS マネージドコンソール上からも可能です。 唯一、 subnet 上にのっているリソースだけ(設定していれば) Local Gateway を通してオンプレミスネットワークと疎通できるところが違います。 EC2 インスタンス EC2 インスタンスを立ち上げる API を見ていただければわかるのですが、 EC2 インスタンスの操作自体は Outpost 上へデプロイする設定を持ちません。 代わりに、 Outpost 筐体上にデプロイしている subnet へ EC2 インスタンスをデプロイすることで自動的に Outpost 筐体上へインスタンスがデプロイされる仕組みになっています。 このインスタンスをオンプレミスネットワークと通信させるようにするには こちらが提供する IP アドレスプールから IP アドレスを割り当てられた ENI Local Gateway へのルーティング がそれぞれ必要になります。 使用感は Outpost 上へデプロイしない他のインスタンスとまったく同じで、こちらも AWS のマネージドコンソールから管理が可能です。 Outposts 上で展開される EC2 で利用可能なインスタンスタイプについては Outpost 筐体注文時に指定します。 現在は m5, g4dn, c5, r5 などが指定可能です。 EBS Volume CreateVolume API を見ると Outpost ARN を指定できるようになっていることが確認できます。 他の EBS Volume とは指定された Outpost 筐体上にデプロイされているところが違うだけで、 Public 上の AWS にあるインスタンスからもマウントでき、シームレスに連携できます。 S3 on Outposts Outposts 上の S3 は S3 on Outposts と呼称されていますが、 S3 と S3 on Outposts は操作感が異なります。 オブジェクトを put する、などの操作にひと手間必要 発行されるアクションが S3 と一部異なる AWS CLI の high level な操作 ( copy 等) は使えない AWS マネージドコンソールからバケット内のオブジェクトが確認できない などの違いがあります。 バケットへの接続 S3 on Outposts では以下のものを VPC に設置しなければバケットへ接続できません。 access point Outposts endpoint これらリソースを設置した上で、それらリソースが設置された VPC からのみアクセスが許可されています 7 。 このように現在の S3 on Outposts を利用するには Public 上の S3 とは違ってひと手間必要になります。 こちらでも使用感がより良くなるよう気になるところは適宜フィードバックした上で改善を待っています。 皆さんも使用した上で気になる部分があれば AWS へフィードバックをお願いします。 API さて、肝心の S3 on Outposts のデプロイについてですが、 S3 とは API が 別 です。 Public AWS 上の S3 にバケットをつくるには Amazon S3 の CreateBucket API です。 一方、 S3 on Outposts にバケットをつくるには Amazon S3 Control の CreateBucket API です。 そもそもからして API が異なります。 他のサービスは同じ API 内にデプロイ先 Outpost 筐体を指定すればよかったのですが、これだけ例外で他のものと API レベルから分離されています 8 。 EKS Outposts 上で Amazon EKS ノードを実行しオンプレ環境でマネージド k8s サービスの利用を可能とします。 マネージド形ノードはサポートされておらず、自らEC2を作成してクラスターにノードとして登録するセルフマネージド型ノードのみがサポートされています。 セルフマネージド型ノードは AWS Outposts にデプロイできますが、マネージド型ノードや Fargate ノードにはデプロイできません。 https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/eks-on-outposts.html また、コントロールプレーンである EKS クラスターは Public 上の AWS における subnet 上に作成する必要があります。 クラスターの作成時に、 Outposts サブネットを渡すことはできません。 https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/eks-on-outposts.html Application Load Balancer Outposts 上で Application Load Balncer(ALB) をデプロイするには c5/c5d, m5/m5d, r5/r5d インスタンスが必要となります。 また、オンプレミスネットワークから接続する場合はこちらが払出す IP アドレスプールから IP アドレスを割り当てる必要があります。 An Application Load Balancer can be deployed on c5/c5d, m5/m5d, or r5/r5d instances on an Outpost. The following table shows the size and EBS volume per instance type that the load balancer can use on an Outpost: 使用感としては普通の ALB と変わりません。 AWS 上のマネージドコンソールからも管理可能です。 容量 Outpost 筐体上に展開できる EBS や S3 ストレージのサイズ、 EC2 のインスタンス数の制限などは当然デプロイしている Outpost 筐体の物理的制約に依存します。 消費しているリソースや、空き容量といったものは Outposts コンソールからデプロイしている Outpost 筐体毎に確認できます。 使用できる空き容量がない場合、容量のないリソースのデプロイはできなくなります。 容量がない時にデプロイしようとするとデプロイは空きがでるまで待たされます。 ちなみに、 EC2 インスタンスの容量上限はインスタンスタイプ毎にあるので、 m5 と g4dn をそれぞれ利用できるようにした Outposts 筐体における EC2 インスタンスの上限は m5 と g4dn で別々です 9 。 まとめ 本記事では Outposts について NTT ComによるAWS Outposts ラックの導入方法について オンプレ環境との統合について Outpostsで提供されるサービスについて について記載しました。 Outposts はその使用感を Public 上の AWS と同じにしつつ、従来は難しかった場面でもクラウドサービスを利用できるようにしてくれる素敵なサービスであることが見ていただけたかと思います。 細かいところで注意すべき点があったりもしますが、それらを差し引いても AWS サービスをオンプレ内で、しかも外にデータを出さずに管理できるというところは魅力あるところです。 データ利用の壁があり、今まで Public クラウド上にあった AWS サービスを利用し辛かったところはこれを機に AWS Outposts の利用を検討してみてはいかかでしょうか? 例えば、サブネットを作る CreateSubnet API にデプロイ先 Outpost 筐体を示すための OutpostArn というプロパティが追加されていますが、 Outposts 専用に API は切られておらず、 Public のものと同一です( https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateSubnet.html#API_CreateSubnet_RequestParameters )。 ↩ 提供されるサービスについての詳細は 公式ページ へ譲ります。 ↩ 「Q: データセンターに設置された Outposts ラックの物理的なセキュリティに対する責任を負うのは誰ですか?」 https://aws.amazon.com/jp/outposts/rack/faqs/ ↩ デフォルトでは VPC と Local Gateway に与えられたルートテーブルは関連付けられていません。 ↩ この IP アドレスプールのことを customer owned IP address または 顧客所有の IP アドレス , CoIP と呼称します。 ↩ customer owned IP address を持たせることは AWS API からはできるようになっていますが、一部 CloudFormation コンストラクターについては未対応です。そのため、 CDK 等を利用して IaC をやるのは現状では少し難しいです。これについては次回以降説明します。 ↩ https://docs.aws.amazon.com/AmazonS3/latest/userguide/S3OutpostsNetworking.html ↩ オブジェクトの操作 API 名はかわりませんが、 API に指定するバケット名は Public 上の S3 とは異なり、 S3 on Outposts 上のバケット名ではなく、バケットへの接続を確保している access point の ARN になります。 ↩ ただし、 EBS 容量は全てのインスタンスタイプで共通してるので、そちらの上限にひっかかる可能性はあります。 ↩
アバター
みなさんこんにちは、社内のエンジニアが働きやすくすることを目標にする Engineer Empowerment プロジェクトの  @Mahito です。 先日 NTT グループのソフトウェアエンジニアを対象とした Git / GitHub の研修を NTT グループのエンジニア有志で行ったので、そのことについてお話しします。 ちなみに、以前に 社内のソースコードをGitHub Enterprise にとりまとめてる話 という記事も書いたことがありますので、興味があればそちらもご覧ください。 背景 本研修のきっかけは、ソフトウェアエンジニアの育成に関して NTT グループ内のエンジニアたちと議論が盛り上がったことです。 現在 NTT グループにはグループのエンジニア有志の集まる非公式なコミュニティがあります。私は不定期に Meetup を開催しているのですが、そこで「 ソフトウェアエンジニア育成 」をテーマにした議論をした際に以下のような意見がありました。 NTT グループ各社でのソフトウェアエンジニア育成の状況は様々だが、上手くいっている会社は少ない 上手くいっている会社や高スキル者のノウハウを NTT グループに横展開することで、グループ内のソフトウェアエンジニアの技術力を高めたい また、タイムリーに 「チームメンバに Git / GitHub を教える必要があるが、どうすればわかりやすく教えられるか」 という話題が上記のエンジニアコミュニティ内で話題になっていました。 最近のソフトウェアエンジニアの必須スキルとして Git / GitHub が挙げられるものの、最初に使うまでの一歩が難しいスキルかも知れないということで、 Git / GitHub を利用したことがない 、または なんとなく使ってみたもののあまり理解できていない という人を対象にした本研修を企画しました。 研修について 今回の研修は、バージョン管理システムである Git と、ソースコードホスティングサービスを提供する GitHub の初歩的な利用方法について、講義とハンズオンを通して理解してもらう形で行いました。 「何を教えるのか」、「研修のゴールをどうするのか」というところを研修スタッフの間で何度も話し合った結果、Git / GitHub で基本となる操作について、実際に手を動かして理解してもらうことにしました。これは初心者の方にはまずは基本をしっかり理解してもらうことが大事だと考えたからです。 以下は実施した研修の内容です。 研修は、日本電信電話(NTT)の研究所、NTTドコモ、NTT Com のエンジニアが講師とサポーターを担当しました。サポーターの役回りはハンズオンのサポートや質問への回答で、私もサポーターとして研修に参加しました。 また、事前の研修企画や取りまとめは私が行いましたが、NTT グループへの周知についてはエンジニアコミュニティや NTT の人事、技術企画の方々の力をお借りしました。 参加募集を始める前は 「50 人ぐらい来ればいいよね」とスタッフで話をしていたのですが、募集開始から 1 週間経たずに 100 人近い応募がありこれ以上の人数はサポートしきれないと慌てて応募フォームを閉め、後日同様の研修をもう一度行いました。 おかげで2回の研修で 170 名ほどに参加してもらい、 Git / GitHub について学びながら触ってもらうことができました。 参加者には事前アンケートに回答してもらっていたため、回答内容からこの研修の参加目的を確認したのですが、以下のような理由が多かったです。 現在バーション管理をしていない文書などをバージョン管理していきたい 現在 Subversion を利用しているが、Git への乗り換えを検討している Git / GitHub の基礎が知りたい 独学で Git を学んだが、GitHub を利用した開発フローを学びたい NTT グループの中でソフトウェアを扱う会社も多くありますが、一方でこれからソフトウェアに関わっていく会社も多く、NTT グループの中でソフトウェアエンジニアの数はまだまだ多いと言える状況ではありません。 しかし、こうした研修を用意することで、これからソフトウェアに関する技術を学ぼうとする方が多くいることを知ることができました。 また、参加者の中にはエンジニアではなく営業やマネージャー職の方が 1 割程度参加しているのも見受けられました。 研修当日の様子 2 回開催した研修は全てオンラインで行いました。 事前アンケートでは参加者の約 1/3 が Git や GitHub を利用したことがないという状況でしたので、研修は時間を多めにとって2時間〜2時間半をかけて行いました。(1 回目のアンケートでペースが早いという声が多くあり 2 回目は時間を延ばしました。) 研修環境については事前に設定資料を配布し設定をお願いしていたことや、なにか問題があった人や質問がある人については Slack でサポーターが対応をしていたため、特に大きな問題はありませんでした。 当日の質問では、研修内容についての質問や、Git / GitHub のユースケースやベストプラクティス、社内でどのように使っているのかなどの質問がありました。 研修後アンケート 研修の満足度(2回目アンケートより) 研修後のアンケートでは、回答してくれた方の多くが満足する形になりましたが、やはり初心者向けの研修だったということもありすでに Git / GitHub を触ったことがある人にとっては物足りない研修だったという声もありました。 研修の難易度(2回目アンケートより) 研修の難易度もアンケートを見る限り初心者向けとしてはいいレベルで実施できたのではないかと思います。 参加者の声 研修に参加した方からは以下のようなコメントをいただきました。 概念の話と実技をバランスよく教えていただき、非常にわかりやすかった ファイルの変更内容が履歴として残るのはあらゆる作業や情報共有で役に立つと思う 今回の学びを他の初心者にシェアできると感じた 改めて基礎がわかりました。応用編も開催してほしいです 一方不満としてはこのような声がありました。 conflictまでは大体よく使うので、もう少し応用例が欲しかった 現状 Subversion 文化、Excel 文化であり、既存プロジェクトへの導入については変更する利点を伝えるコストや教育コストの方が大きいように思われる また、次回以降どんな研修を期待するかという質問については以下のような様々なコメントをいただきました。 Git のより高度な使い方や応用例 アジャイル開発 クラウドの利用方法 機械学習 セキュリティ etc... 今回の研修では参加者に 「Git / GitHub で基本となる操作について、実際に手を動かして理解してもらう」 というテーマで行いましたが、アンケートからこれは達成できたのかなと思いました。 一方で、 Git / GitHub に対する発展的な研修や、それ以外の技術に関する研修への期待や要望というものも明らかになりました。 今回のアンケート結果は NTT の人事などにも共有をして、NTT グループの中で技術研修が求められていることを伝えています。 また、現在こうした意見に応える形で、NTT グループのエンジニアコミュニティの力を借りながら、次の研修の企画を進めています。 まとめ 今回の取り組みは、NTT グループのエンジニア有志が NTT グループのエンジニアの声を拾いつつ、主体的に研修の企画・開催することで、エンジニアの成長を助けるような取り組みのきっかけを作ることができました。 今回は Git / GitHub を対象とした研修になりましたが、アンケートでは様々な技術についての研修を開催してほしいというリクエストもいただいており、現在次の研修をエンジニアコミュニティの面々が企画をしてくれています。 上述した Git / GitHub の発展的な研修も含め、NTT グループのソフトウェアエンジニアが互いに持っているノウハウを共有することで、NTT グループや日本のソフトウェアエンジニアリングをもっと盛り上げていければと考えています。
アバター
はじめに はじめまして。イノベーションセンターの西野と申します。セキュリティ運用改善サービス「Metemcyber」のプロジェクトリーダーを担当しています。 本記事は、NTTコミュニケーションズの職場体験型インターンシップに参加していただいた学生お二方からの寄稿になります。どうぞよろしくお願いいたします。 今村さん はじめまして。今回Metemcyberプロジェクトに配属されたインターンシップ生の今村(GitHub: apeiria-zero )です。現在大学3年生で、大学ではBitcoinを用いたマイクロペイメント技術や、Hashed Time Lock Contract(HTLC)に関する研究をしております。 私はこれまで大規模な開発経験が無く、ほとんど個人での開発経験しかありませんでした。ですので、実際の開発がどのようなものか気になっていました。大規模開発に関われる機会を探していたとき、NTTコミュニケーションズの職場体験型インターンシップの存在を知り、よりリアルな開発経験を積むことができると思い応募しました。 木村さん はじめまして。今回Metemcyberプロジェクトに配属されたインターン生の木村(GitHub: keigokimura )と申します。現在、大学院でブロックチェーンのセキュリティ、NFTの不正取引に関する研究を行なっています。 最近やたらとニュースになっているNFTについて、自分は研究で扱っているけれど、企業ではどのように捉えているのか?そもそも新しい技術に対し、どういう切り口で関わっていくのか?ということが気になっていました。 そんな中、NTTコミュニケーションズのインターンシップのコースの中に、NFT技術を用いた研究開発を行うコースを見つけました。これは企業の方の考えを知るチャンスであり、「自分の研究が企業でどう生きるのか」「企業での研究ってどういう視点で技術を捉えるのか」といった自分の疑問を明らかにする機会だと考え、チャレンジすることを決めました。 インターンシップの概要 今回私たちは、2週間のインターンシップに参加し、イノベーションセンターでセキュリティ運用改善サービス「Metemcyber」の研究開発を行いました。 Metemcyberとは ここでは、Metemcyberのサービスの目的について説明します。 www.metemcyber.ntt.com Metemcyberの目的は、組織におけるセキュリティ運用の健全化を目指すことです。一般的なセキュリティ運用改善サービスと異なる点は、「食生活改善のようなセキュリティ運用の改善」を行える点にあります。 食生活の改善には、日々の食事記録を管理し、栄養バランスなどを元に食生活における問題を見える化することが必要です。さらに、見える化された問題に対し、健康栄養士の方からのフィードバックをいただくことができれば、ユーザはそれを元に食生活を改善していくことができます。 食生活に関していえば、これを実現するサービスとして「 あすけん 」と呼ばれる有名なサービスがあります。しかし、セキュリティ運用において、このようなサービスは現状存在していません。 Metemcyberは、食生活改善のようなセキュリティ運用の健全化を提供することを目標とします。サイバー攻撃のインパクトが大きくなる中、実際のサイバー攻撃を踏まえた正しいセキュリティ対策を講じ、健全に運用できているかどうかは、組織の信頼に関わる大きな問題となります。しかし以下の理由から、組織のセキュリティ運用が健全に行われているかを把握することは難しいと考えられます。 セキュリティ対策はその貢献度や大変さが成果として見えにくい 定期的な監査では日常的な運用が健全なのか分からない 脅威の発見や対策の根拠となる情報が十分だったのか把握できていない そこでMetemcyberは、食事記録のようにサイバー脅威情報の利用を記録し、それに対するセキュリティアクションを管理することで、組織におけるセキュリティ運用の健全性を見える化します。また、見える化によってアクションの達成度が明らかになるため、アクションを行った組織あるいはエンジニア個人を評価できます。Metemcyberはアクションを実行した人にバッジを付与することで評価します。 実際にMetemcyberは図1の3つのコンポーネントから構成されています。 図1 Metemcyberを構成する3つのコンポーネント セキュリティ運用の健全化を見える化するだけでなく、頑張った人を評価できることも、セキュリティ対策へのモチベーション向上に繋がりそうです。 開発環境や期間中のコミュニケーション ここでは実際に今回のインターンで行った内容を説明します。インターンでは、主にFastAPIというWebフレームワークを利用したAPI開発を進めました。 全日リモート形式での実施 インターン前日に参加のための備品が到着 NTTコミュニケーションズから社内連絡用のPC インターン期間の昼食代が入金されたカード Metemcyberの開発自体はGCP上に用意されたVM環境でVScodeからアクセス チームの方々とはSlackでやり取りをし、ソースコード管理にはGitHubを利用 図2 GitHub上のやりとり また、昨日の進捗と本日の予定を報告し合う「朝会」というミーティングに毎朝参加させて頂きました。 他にも様々なミーティングに参加させていただき、現場のリアルを知ることができました。 今村の成果:アクションの記録からメタデータ取得まで Threat Connectome開発の背景 今村が担当したアクションログの生成の部分について説明します。アクションログの内容の前に、Threat Connectomeを改めて説明します。Threat Connectomeとは、「セキュリティアクションを頑張った人をほめたたえたい」をコンセプトに開発されたプラットフォームです。 セキュリティアクションをもとにバッジを発行し、セキュリティアクションを見える化することで貢献を評価します。 図3 Threat Connectomeの目的 アクションログの概要 アクションログというのは、セキュリティアクションの情報やそれを実行したユーザー情報等を含むログの名称です。今回今村が担当した業務は、「セキュリティアクションの記録」と「バッジ発行に必要な情報を生成」する機能の開発です。大まかな全体像は図のようになります。アクションログを生成する create_log 、アクションログ一覧を取得する get_logs 、メタデータを生成する get_metadata という3つの関数があります。まずはアクションログを生成する create_log を説明します。 図4 actionlogsの全体像 アクションログの生成(create_log) create_log の目的はセキュリティアクションからアクションログを生成することです。入力として、アクションID、トピックID、ユーザーIDと実行時刻(入力は任意)があります。これらの情報をもとにアクションログを生成します。 図5 アクションログ生成の流れ 図6 アクションログの内容 アクションログ一覧の取得 get_logs の説明です。先ほどの create_log で作成したアクションログの一覧を取得します。(図7) 実行時刻( executed_at )と、レコード作成時刻( created_at )のタイムゾーンがUTCであることを末尾に情報をつけることで明確化しています。また、デフォルトのソートはlatest順、つまりレコード作成時刻( created_at )の新しいレコードが一番上へ来るようにしています。 図7 アクションログ一覧 バッジ発行に必要なメタデータの生成 最後にアクションログからバッジ発行に必要なメタデータを生成する get_metadata の説明です。アクションログのロギングIDをパラメータとし、バッジ画像やバッジ名が生成されています。imageにバッジ画像のURLがありますが、アクセスすると画像が表示されます。実際のレスポンスは図8のようになります。 図8 メタデータの内容 アクションログまとめ 以上が私の活動でした。まとめとして図9を載せます。セキュリティアクションからメタデータの生成ができたので、この情報をバッジ発行のAPIへ渡します。インターン前はセキュリティアクションとバッジ生成のAPIが分離していたので、そこを繋げる事が出来ました。以降の処理は木村さんの担当に移ります。 図9 アクションログからメタデータ取得まで 木村の成果:バッジの発行からNFT化まで 3. バッジの生成 セキュリティバッジの発行 次に木村が担当したバッジ生成の部分について説明します。Metemcyberではセキュリティアクションを実行した人にバッジを付与することでセキュリティ運用者を評価します。今回は前節で説明があった、セキュリティアクションのログ情報からそれに応じたバッジを発行します。 まず、Threat Connectome内で発行されるバッジをセキュリティバッジと呼び、メタデータからセキュリティバッジを発行できる機能を作成しました。APIとしては以下のようなものを用意し、発行したバッジには固有のIDが付与されます。機能としては図10の2つを用意しました。 図10 Threat Connectome セキュリティバッジ関連の機能 入力としてアクションに関するメタデータを渡してあげると、結果として図11の情報を持ったセキュリティバッジが発行されます。内容としてはそのアクションの実行時間や概要など、アクションに関する情報などが入っています。 図11 セキュリティバッジの内容 セキュリティバッジのNFT化 これでセキュリティアクションを実行した人をバッジによって評価が可能になりました。次に、バッジを受け取った人が自分の成果として示せるように、ブロックチェーン上にセキュリティバッジをNFTとして発行する機能を実現します。最近ではブロックチェーン上でアート作品やデジタルコンテンツがNFT化されて売買されており、NFTは注目を集めています。そんな注目されるNFTとしてセキュリティバッジを発行でき、注目の的になれば、エンジニアの勲章としてすごく良さそうです。 NFTに関する機能としては図12の2つを用意しました。 図12 Threat Connectome NFT関連の機能 入力としてセキュリティバッジのIDを渡してあげると、セキュリティバッジがNFTとして発行されます。図13には発行した結果を示しており、NFTの共通的な情報と、今回のユースケース固有の情報に分かれています。 図13 NFTの内容 また、今回はセキュリティバッジとNFT化されたバッジの紐付けも行っています。セキュリティバッジのフィールドの中に nft_id というものがあり、そのセキュリティバッジがNFT化されていれば、NFTの固有IDが入るようになっています。今回は上記で発行したセキュリティバッジをそのままNFT化しました。図14においてセキュリティバッジの一覧取得機能を用いて再度確認してみると、先ほどは null だった nft_id に値が入っており、しっかり紐付けが行われていることがわかります。 図14 セキュリティバッジ内のNFT ID NFTバッジを手元のMetaMaskにインポートしてみる 最後に自分が手に入れたセキュリティバッジをMetaMaskにインポートしてみます。NFTバッジの情報をもとに自分のMetamaskに登録していきます。図15のように表示できていることがわかります。これだけで達成感が湧いてきますね。 図15 NFTをMetaMaskに登録 4. インターン通しての成果 今回のインターンを通して、図16の流れでセキュリティアクションからバッジを発行する機能を実現しました。 図16 インターンを通しての成果 NTT コミュニケーションズのインターンを終えた感想 今村からの振り返り 今回私はこれまでやってきた個人レベルでの開発と、企業レベルでの大規模な開発がどのような点で違うのかを体験したいと思いインターンシップに参加させていただきました。約2週間NTTコミュニケーションズの現場で開発作業を行いましたが、最も重要だと感じた点はチームで決めた方針に沿いながらコードを書いていくことです。 あたりまえの事かもしれませんが、ずっと個人で開発してきた私はかなり苦労しました。個人の開発では個人のアウトプットに最適化すればいいのですが、チームの開発ではチームのアウトプットに最適化しなければなりません。きちんとチーム内でコンセンサスを取り、他の方が担当してる箇所に影響が出ないか。それを吟味しながらの開発は想像以上に重要であり、個人開発ではなかなか意識できない体験ができました。 今回のインターンシップを通して、現場で働くプロの方々の開発の進め方を少しでも自分の中に落とし込んで今後のエンジニア人生に活かしていきます! 木村からの振り返り 今回自分は大学院で行う研究と比べて、企業ではどのような視点で研究開発を行っているのか、自分の研究を将来企業でどのように役立てることができるのかを知りたいという思いでインターンシップに応募しました。そんな思いで2週間Metemcyberの開発に携わる中で、企業での研究開発は常にユーザを見据えて開発を進めることで、そのために必要であり最も有力であると考えられる技術を剪定するということを学びました。 インターン前は、企業の研究開発は新しい技術を使って何かできないかといった視線で開発していると思っていました。しかし、インターンシップの中で、ブロックチェーンやNFTは新しい技術だから研究開発しているのではなく、それが価値を生み出せるから使っていることを知りました。またそういう考えの上で、それぞれの技術が課題解決に対し本当に最適なのかはしっかり見極めなければならないと学びました。 そんな学びを得た上で、今はMetemcyberが今後広がっていき、MetemcyberのNFTバッジがエンジニアの評価の一基準になっている姿を想像するとワクワクしてきます。 今回のインターンシップを通して、そのプロダクトがどういったユーザを想定し、どういった価値を届けられるのかといった要件定義の部分については現場を見てしっかりと学ぶことができました。この経験を踏まえ、今後もエンジニアとして成長していきたいと思います! トレーナーからのコメント トレーナーの西野です。今村さん木村さん、お二人ともお疲れ様でした。 今村さんはエンジニアとしての初心を見つめ直す鋭い質問や、学生ならではの観点からチームの開発環境を見直す素晴らしい機会を提供してくれました。個人での開発とチームで行う業務の違いに戸惑った部分もあるかもしれませんが、見事克服して素晴らしい成果を残してくださったと思います。本インターンが、エンジニアとして羽ばたくためのお手伝いになったのであれば幸いです。 木村さんは優れた課題解決能力を活かし今回のインターンを牽引してくれた存在でした。研究開発業務においても、「プロダクトアウトやマーケットインを踏まえた実装がより社会貢献できるものにつながる」と肌で感じてもらえたことは、トレーナーとしても嬉しい限りです。その高い能力を発揮し、社会に貢献できる素晴らしいエンジニアとして邁進されることを祈念しております。 お二方の技術的な貢献を社会に役立つサービスとして世に出せるよう、Metemcyberプロジェクトでは引き続き研究開発を進めていこうと思います。ご参加いただきありがとうございます。
アバター
はじめに こんにちは、Things Cloud のカスタマーサクセスチーム 伊藤、佐々木、高橋、三橋です。私たちは、データプラットフォームサービス部で IoT プラットフォーム「 Things Cloud 」のサービス開発やお客様への技術支援を担当しています。 さて、皆さんはデータを見やすいようにグラフ化したり、表にまとめる時はどんな方法を使っていますか?最近では、JupyterLab や Python ライブラリの Pandas を利用されている方が多いかもしれませんね。私たちのチームでは、 Observable という Web サービスを利用しています。Observable を使うと、下図のようなカラフルで見やすいグラフを短時間で作成できます。 本記事では、Observable を活用した IoT データ可視化における探索と、私たちが携わっている IoT プラットフォーム「Things Cloud」との親和性について紹介します。 出典: https://observablehq.com/@d3/gallery Observable は Web ブラウザ上で動作する JavaScript ランタイムを提供するサービスで、ノートブックと呼ばれる単位でコードを管理できます。Observable のサイトにあるように、データを扱いながら、創る・コラボレーションする・学習するための場を提供しています。 IoTデータの可視化について Observable を活用した具体的な事例紹介へ入る前に、IoT の世界でデータを可視化することの重要性を説明します。 IoT の世界では、モノの世界とコトの世界の大きく2つの要素に分類できます。下図は簡略化した2つの世界のイメージです。 IoTはモノの世界とコトの世界から構成される モノの世界 モノの世界には、スマートフォンやトラクター、工場のラインなど管理対象となるモノや場所と、それらに取り付けて現実世界の情報を電子データに変換したりクラウドにデータをアップロードするセンサーやデバイスが存在します。モノの世界では、センサーデータだけを管理するのではなく、センサーを取り付けたモノや場所、またモノや場所同士の関係性を管理することが重要です。 (例: 【センサーと場所の関係】会議室Aに取り付けた人感センサA、【場所と場所同士の関係】ビルAの会議室A) コトの世界 コトの世界には、人間が IoT データを閲覧したり、モノの世界に対して制御するためのユーザインタフェースや、ユーザインタフェースを見る人間(エンドユーザ、業務オペレータ、業務の管理者など)が存在します。 クラウドに蓄積されたデータは単なるデータとしてではなく、何らかの意味付けをしてユーザが直感的に理解しやすい形に表現する必要があります。わかりやすく表現されたデータを見ることで、例えば工場を遠隔監視しているオペレータが、メンテナンスが必要な時に適切なタイミングで指示を出したり、機器の不調傾向を分析したりするなど業務上必要なアクションを実行するのに役立てられます。 様々なセンサーデータが存在する中で、オペレータやデータを見る人にとって IoT データを適切な見せ方に作り上げることを1ステップで済ますのは難しく、試行錯誤が必要な領域です。単なるセンターデータの折れ線グラフが最適な場合もあれば、ヒストグラムが適している場合もあったり、さらに視覚的に見せるためにバブルチャートのようなものが適している可能性もあります。 次の章では、主にコトの世界におけるIoTデータの最適な見せ方を探求する上で、学習コストが低く、かつスピーディにデータ可視化を試行錯誤できる方法として Observable を紹介します。 Observableの紹介 ここでは、Observableでのデータ可視化について簡単に紹介します。 Observableについて Observableは、ブラウザ上でJavaScriptをセル単位で記載し、グラフ等を容易に作成できるWebアプリケーションです。( JupyterLab のJavaScript版のようなイメージとなります。) JupyterLab同様、セル単位で実行できます。またJupyterLabと違い、セルの実行順序を意識する必要はありません。 Observableの概要に関しては以下が参考になります。 5分でわかる概要 Tutorial (日本語) また、Observable 公式が公開している Observable の使い方動画も参考になると思います。 グラフ表示を試してみる Observableではグラフの種類変更や閾値線の表示等を容易に行うことができます。例えば、Observableのグラフ表示メソッドである Plot を用いると、簡単にグラフを表示させることができます。 Plotに関する例については Observable Plot をご参照ください。 下記は棒グラフを表示させるセルとなっています。 Plot.plot の引数へはオブジェクト型の変数を指定し、その中へ Plot: Marks という、グラフを表示させる主体となる値を格納しています。 Plot.plot({ marks: [ Plot.rectY(aapl, Plot.binX({y: "count"}, {x: d => Math.log10(d.Volume), normalize: true})), ] }) 今回は棒グラフ(時系列)を表示させるので、marks内で Plot: Rect を指定しています。 ここでは、 Plot: Rect を例にグラフを表示させています。グラフ表示のために使用しているデータは、Apple株の1日あたりの取引量となります。 ここから試行錯誤の代表的な例として、グラフの種類変更及び閾値線を表示します。 グラフを変えてみる グラフの種類を変更するには、 marks 内へ指定する値を変更します。 棒グラフを表示するために Plot.rectY を指定していましたが、折れ線グラフを表示するように変更してみます。 折れ線グラフを表示させるには、 Plot.line を指定します。 以下では、 Plot: Line を例にグラフを表示させています。 グラフ表示のために使用しているデータは、先ほどと同様、Apple株の1日あたりの取引量となります。 Plot.plot({ y: { grid: true }, marks: [ Plot.line(aapl, {x: "Date", y: "Close"}) ] }) 閾値線を表示してみる 閾値線を表示するには、 marks 内へ Plot: Rule を追加します。 ここでは、y軸の値が100の箇所へ赤い閾値線を表示します。 Plot.ruleY の第一引数へ、配列型式で閾値線を表示させる値を指定し、第二引数へオブジェクト形式で色の指定等を行います。 Plot.plot({ y: { grid: true }, marks: [ Plot.line(aapl, {x: "Date", y: "Close"}), Plot.ruleY([100], {stroke: "red"}) ] }) Things Cloud 紹介 Things Cloud(R) サービスについて、簡単にご紹介します。 Things Cloud(R) は、NTT Communications の Smart Data Platform (略称SDPF) の中で、データ収集機能を提供するプラットフォームです。 センサーにつながる IoT デバイスからデータを収集し、蓄積できるクラウドサービスで、簡単な操作でデバイス情報を閲覧したりデータの可視化を行ったり、ユーザー管理/通知/デバイス管理等、IoTに必要な機能一式が揃っています。 Things Cloud Things Cloud の提供機能(明るい青の部分) データ可視化の原理原則について ここで、可視化するデータをどのような形にすればグラフに読み込ませやすいか、データを可視化する際にどのようなグラフで、またはどのような配色を選択すればより効果的に表現できるか データ可視化の原理原則について説明します。 クレンジングと前処理 一般的に、データを可視化、分析をする際には、元となるデータ(自身で収集したデータ、オープンデータなど)を目的の可視化や分析に適した形に処理する必要があります。データを適した形に処理するという工程は、「クレンジング」と「前処理」から構成されています。以降では、クレンジングと前処理について説明します。 出典: ©︎矢崎裕一 クレンジング クレンジングは、データの可視化や分析ができるように、最低限のデータ処理を行う行為です。主に、構造と品質の観点でクレンジングを実施します。 構造観点 データ形式を変える マトリックス形式のデータは扱いづらいため、リスト形式のデータへ加工する 参考: 整然データとは何か データセットの形を理解する 扱うデータセットが ロング型 もしくは ワイド型 かを理解する。必要であれば適した型へ変換する ロング型:データが増えると行が増える。基本的にロング型にしておくとデータ可視化・分析しやすい ワイド型:データが増えると列が増える。年度ごと/国ごと/都道府県ごとに列が増えるパターンが多い 参考: R for Data Science - 12 Tidy data 品質観点 基準にもとづき、現状の品質を把握する 正確性(適合性): データに表記の揺れはないか? 完全性: データに欠損はないか? 一貫性: データに不整合はないか? 信頼性(精度): データに誤りやノイズはないか? 重複度: データに重複はないか? 参考: データ品質の国際基準 ISO/IEC 25012:2008(en) 参考: データ分析に必要なデータクレンジングの方法 前処理 前処理は、目的の可視化や分析を実施するために、積極的にデータを処理、加工する行為です。例えば、以下のような場合はデータの前処理が必要です。 地図上にデータを描画したい。データに住所は存在するが、緯度経度の情報が存在しない → 住所やその他既存データと組み合わせて緯度経度を算出する前処理が必要 日本の人口を表すヒストグラムを表示したい → データから階級幅を計算する前処理が必要 可視化 クレンジングと前処理を行い整えたデータを、意図的に最適なグラフの種類や色を選択することで、より効果的に見せることができます。では、どのように可視化すればより伝わるかについて説明します。 分類 さまざまな状況によって最適なグラフを選ぶために役立つグラフの分類として Financial Times の Visual Journalism Team による Visual Vocabulary があります。このようなリソースを利用することで可視化するデータを表現する最適なグラフを選択するのに役立てることができます。 出典: https://www.ft.com/about-us 9つに分類されたカテゴリーの中で、どのような時にどう可視化するかを さまざまなグラフでそれぞれ説明されています。 その他にも、 visualizing.jp という Web サイトではデータ可視化にまつわる実践的な手法や実例が多く紹介されています。気になった方は是非ご覧ください。 Things Cloudでは Things Cloudのグラフ分類も、この原理原則に沿っています。 Measurementのような時系列データの表示では、時系列変化を表す折れ線グラフで一定期間内の変化を表す手法を選択しています。 カスタムウィジェット化した複数データの相関関係の表示では、相関関係を表す散布図グラフで2項目の関係を示す手法を選択しています。 色 データ可視化の際、色により意味や雰囲気を効果的に伝えることができます。データの性質により表現したいものに対応した色の使い方を適用すると、データの性質を損ねずに視覚的に表現できます。一方、順序づけのあるデータに定性的な色を使用すると大小の関係がわからなくなったりと視覚的な錯覚を起こし見誤らせる原因にもなりうるため、注意が必要です。色とデータの種類には以下の対応があります。 色 データ 効果 定性的 順序づけしないデータ (都道府県など) 上下関係がなくなる 連続的 順序づけするデータ (成績など) 上下関係を表現できる 分岐的 数値データのゼロなどを基準にして、増加と減少があるデータ (人口の増減など) 分岐を表現できる Things Cloudでは Things Cloudの配色についても、この原理原則に沿って効果的な色を選択しています。 Measurement のデータポイントのように順序づけする必要のない場合は、定性的な色を選択しています。 CO2濃度可視化でカスタムウィジェット化したヒートマップのように順序づけのある場合は、連続的な色を選択しています。 CO2ヒートマップは、 OPEN HUB でご覧いただけます。 gradient: {0.2:'green', 0.5:'yellow', 0.8:'orange', 0.9:'red'} この原理原則に従い、Observable で ヒストグラムを表示してみる 私たちは、このデータ可視化の原理原則に従い、Things Cloud に報告されるデバイスのセンサーデータ(今回はシミュレートしたデータ)を使って、Observable Notebookで エンジンスピードとエンジン警告の関係性を ヒストグラムで可視化してみました。 まずは、Things Cloud の センサーデータ は、品質観点ではデバイスのソフトウェアによりある程度担保されていますが、構造観点ではグラフに読み込ませやすい形ではないため、以下のようにセンサーデータのデータ構造を簡易化するようなクレンジングを行いました。 // Measurement 配列でのオブジェクトの階層を平易化。この構造になっていれば Observable での基本的な可視化が可能 // この時点でのデータ構造: [ {type: "com_EngineMeasurement", com_EngineMeasurement_speed_unit: "rpm", com_EngineMeasurement_speed_value: "1000", time: "..."}, {type: "com_EngineMeasurement", com_EngineMeasurement_speed_unit: "rpm", com_EngineMeasurement_speed_value: "2000", time: "..."}, {type: "com_EngineMeasurement", com_EngineMeasurement_speed_unit: "rpm", com_EngineMeasurement_speed_value: "2500", time: "..."} ] 次に、エンジンスピードとエンジン警告の関係性を描画する際に、一定のスピード幅における警告数といった前処理にあたる計算を行う処理が必要ですが、これは前述したPlotライブラリで前処理含めて描画できるため、以下のように前処理から可視化までを行いました。 Plot.plot({ y: { grid: true }, color: { legend: true }, marks: [ Plot.rectY(measurements, Plot.binX({y: "sum"}, {x: "com_EngineMeasurement_speed_value", y: "com_EngineMeasurement_warning_value", fill: "source_id"})), // 前処理(ここではPlotが行ってくれる) Plot.ruleY([1000], {stroke: "red"}) ] }); Plot.rectY() の第一引数に クレンジングした Measurement のオブジェクトの配列( [{measurement1}, {measurement2},...] の形式)を与えることによって、Plot ライブラリが自動的に階級幅などを算出、グラフを描画します。 Things Cloudへの転記 ここまで、Observable で、あれこれと試行錯誤しながら表示したグラフを そのまま Things Cloudで実現してみませんか? Things Cloud には、標準で搭載されていて簡単に表示できるIoTデータを可視化する ウィジェット に加え新しい可視化として、今回試行したヒストグラムのように表示したいグラフのウィジェットをカスタマイズして、追加し利用できます。 私たちは、Observable で Plotライブラリを利用して試行錯誤したNotebookのJavaScriptのコードをそのままウィジェットに転記できるようなカスタマイズ用のテンプレートを作成しました。 ご興味のある方は、Things Cloud の 開発者レポートで、 Observable を活用したIoTデータ可視化の探索からカスタムウィジェット開発までのチュートリアル を紹介していますので、ぜひ ご覧ください。 ※ Things Cloud のご利用には契約が必要です。ご利用を希望されるお客様は iot-info@ntt.com までお問い合わせください(お手数ですが@を半角文字に置き換えてください)。 おわりに いかがでしたか? IoTデータを取得した後の可視化で、Observable によりプロトタイピングし、ある程度固まったら Things Cloud にコードを反映というサイクル、とてもイメージが膨らみますね。メンバーも使ってみて、 Observable の対話性、Plot 等のライブラリのパワーに夢は広がり、興味が尽きません。 Things Cloud カスタマーサクセスチームでは、ますます広がっていく IoT ユースケースを広げ、より便利な使い方を開拓すべく内製開発を行っています。今後もIoTに関連する開発ネタを発信していきたいと思っています。 問い合わせ先 お手数ですが@を半角文字に置き換えてください。 Things Cloud サービスについて:iot-info@ntt.com Things Cloud カスタマーサクセスチーム:iot-app@ntt.com
アバター
はじめに 2月14日から25日までの2週間、NTTコミュニケーションズのインターンシップに参加させていただいた八木です。普段は大学院で画像処理の高速化に関する研究をしています。インターンシップでは技術コースのうち「 AI/MLシステムとの統合を志向した、メディアAI技術の研究開発 」ポストに応募しました。全日リモートでの参加で、joinしたチームのマルチA100 GPUサーバなどを用いて画像認識モデルを学習し、NTT Com で独自に構築しているデータセットでその性能評価をしました。この記事では、その体験談を記載します。 インターンシップまでの経緯 就活イベントで NTT Com の紹介を聞いたのですが、そこで色々と説明してくださった社員の方からメディアAI技術開発チームを紹介してもらい、後日今回のメンターさんらと懇談していただきました。その際、チームの紹介を受け興味を持ち、このインターンシップに応募しました。面談で期待することを聞かれた際には、取り組みに用いるデータサイズに対し適切な計算リソースを提供してほしいと伝えたのですが、その希望が反映されたテーマだったと感じます。インターンシップの中ではチームが持つGPUサーバ群の紹介もしていただき、それらがラッキングされた NTT Com のデータセンターを写真(例えば以下)で紹介いただく機会もありました。 インターンシップで取り組んだこと インターンシップでは、大きく以下の3つに取り組みました: 公知の人物検出データセットを複数組み合わせて学習データの規模を大きくする 構築した学習データを用いて人物検出器を学習する NTT Com が内製で構築しているデータセットを用いて学習したモデルを評価する 公知のデータセットにはいくつか選択肢があったのですが、今回は COCO と CUHK-SYSU を選びました。人物検出に関する各データセットの統計を以下の表にまとめます。 平均物体サイズは、物体(人物)バウンディングボックスの画素数をそれが含まれる画像の画素数で割って算出しました。画像数・物体数ともにCOCOが最も多いですが、CUHK-SYSUは画像の解像度が高めな一方で物体は小さめに映り込んでいることが分かります。NTT Com で内製構築しているデータセット(NTT Com 内製)の平均物体サイズは、それらのおよそ中間でした。 COCOとCUHK-SYSUはアノテーションのフォーマットが異なるため、そのままでは1つのデータセットとして用いることができません。そこで今回は、CUHK-SYSUのフォーマットをCOCOのそれにコンバートし、かつそれをCOCOの人物アノテーションと結合することで1つのアノテーションデータを構築しました。コーディングにはPythonを使用しました。CUHK-SYSUのフォーマットを解読したり、複数データセットのインデックスを正しくアラインメントしたりするのはなかなか大変で、アノテーションを可視化してフォーマットの解釈が正しいか確認したり、またメンターと画面共有しながらデバッグしたりもしました。 モデル学習速度の比較 そんなこんなで構築したデータセット(COCO + CUHK-SYSU、総画像枚数64115+18184=82299枚)を用いて、まずは異なるGPUサーバでモデルの学習速度を比較しました。結果を以下の図に示します。異なるGPUサーバとして、チームが持つV100 GPUを4基搭載するサーバ(V100 x4)、A100を4基搭載するサーバ(A100 x4)およびA100を8基搭載するサーバ(A100 x8)を使用させていただきました。人物検出モデルはFaster R-CNNで揃えています。 図から、どちらのバッチサイズのケースでも、バッチあたりの学習にかかる時間はV100 x4よりもA100 x4の方が短く、A100 x8では更に短いことが分かります。具体的には、A100 x4はV100 x4の1.4倍程度、A100 x8はA100 x4の1.7倍程度高速でした。大きなバッチサイズですと当然バッチあたりの時間は増えますが、2倍のバッチサイズに対しかかる時間は1.8倍程度に抑えられていました。どの条件でも12エポック学習を回しましたが、V100 x4で9時間程度かかっていた学習がA100 x8では3.5時間ちょっとで終わりました。 A100速い 。 NTT Com 内製データセットを用いた性能比較 続いて、データセットを組み合わせてそもそも人物検出の精度が向上しているのか確認するため、NTT Com 内製データセットをテストデータとして性能評価をしました。比較のため、COCO・CUHK-SYSUいずれかのみを学習データとして得られたモデルでの評価もしています。以下の表に、Intersection-over-Union (IoU) の閾値を0.5と設定したときのAverage Precision (AP)を示します。 表から分かるように、CUHK-SYSU単独よりもCOCO単独の方が検出性能は高くなります。けれども、それらを1つのデータセットとして組み合わせることで、APが更に0.02以上向上しています。より規模の大きなデータセットでモデルを学習することは、実際に性能向上に寄与することが分かります。なお今回の例では、バッチサイズを16よりも8とした方がわずかながら性能は高い傾向でした。 検出結果例を以下に示します。人物が小さめに映り込むCUHK-SYSUをCOCOと組み合わせてモデルを学習することで、COCO単独の場合に比べ、小さな物体の検出漏れが減っている印象を受けました。 インターンシップの感想 普段は主にC++/CUDAを用いて、深層学習とは直接関連しない画像処理を高速化する研究をしているため、Pythonを用いた深層学習は不慣れな点がありました。しかし、メンターやチームの皆さんのサポートもあり、内部の成果発表までできました。トライ&エラーを含めると結構な回数の学習を回しましたが、期間中にまとめられたのはjoinしたチームが潤沢な計算リソースを有していたことも大きかったと思います。貴重な体験をさせていただき、ありがとうございました。 メンターからのコメント メンターを担当したイノベーションセンターの田良島です。八木さんインターンシップお疲れ様でした!今回のインターンシップはコロナの影響で完全リモートでの開催でした。チームメンバーとのコミュニケーションはオンラインのみで、また大学での研究とは毛色の異なる作業もあってきっと大変だったと思うのですが、根気強く課題に取り組んでいただいてとても感謝しています。複数のV100やA100が搭載された我々のGPUサーバに触れていただけたことはよかったのかなと感じています。本当は、NTT Com のデータセンターに来てもらい、GPUサーバ群を見学いただけるとよりよかったのですが、今回は残念ながら叶いませんでした。 このインターンシップでの経験が今後の八木さんの研究やキャリアに少しでも役立てば嬉しいです。ありがとうございました!
アバター
こんにちは! 今年もう2ヶ月ほど経ちましたがまだまだ寒い日が続いていますね。 イノベーションセンターの原田です。 本日は2021年11月頃に実施しました新入社員研修の取り組みについてご紹介します。 研修の概要について このハンズオンは新入社員が半年ぐらい業務に携わった頃に おそらく対峙する or したであろうレガシーなコードについて、 どのように立ち向かうべきか?を5日間チームで手を動かしながら学習していただく研修です。 この研修には2つのゴールがあり、1つ目が「レガシーコードを安全に変更するための土台作りの方法を学ぶ」、2つ目が「どのようにレガシーコードを戦略的に改善していくか意識付ける」です。 受講生は 20 名で、2021 年度入社以外の社員も数名参加しました。 また講師・メンターを社員7名が担当し、 研修は全体を通してオンラインで行いました。 下記は全体のスケジュール表になります。 こちらの表の通り、 まず1日目にレガシーコードをドンッ!と配って仕様を理解した後、 2日目でテストコードを用意し、3日目からリファクタリングや新規機能の実装に入ります。 また演習の前後にはチーム間レビュー(隣のチーム同士で意見交換)とふりかえり(KPT)を用意しており、 他のチームが得た知見や気づきを共有する場を設けています。 なお研修のジャンルとしてソフトウェアが中心となりますが、 新卒研修として NTT Com では他にもネットワーク研修、AI・データサイエンス研修などがあります! なぜこの研修を実施したか ソフトウェアをテーマとした新卒研修は毎年行われていますが、 昨年までの内容が IaaS や CaaS、CI/CD が中心でコードを書くことがメインでは無かったため、 新入社員から「もっとコードを書きたかった!」と意見を頂いたことがきっかけとなります。 また、NTT Com ではNeWorkなど内製開発に注力していますが、 社員が開発中の悩みをヒアリングした結果 既存のコードについてのリファクタリング・リアーキテクチャに対する悩みを持っていることが分かりました。 よって本研修を通してレガシーコードに対するアプローチ方法を学ぶことで開発力に磨きをかけ、 エンジニアとしてのキャリアを形成する後押しができればと思い企画しました。 研修の一例について ここからは各日の研修の流れを簡単に紹介していきます。 Day1 レガシーコードを読む レガシーコードの定義から説明し、どのようにコードを読み進めていくか解説しました。 なおレガシーコードとして N-PTC 2020 で利用した コード を題材として取り上げ、受講生は手探りで仕様を理解していきました。 Day2 テスト可能にする 1 日目に読み込んでもらったレガシーコードについて、手を加える前にテストを書きます。 テストとしては単体テスト・結合テスト・受け入れテストなどをそれぞれ実装してもらいましたが、 まずは結合テスト(API テスト)から実装してもらいました。 もちろん一日で全てのテストを書ききることは難しいため、3 日以降もテスト実装に時間を割いても OK にしています。 Day3 レガシーコードの継続開発 おおよそテストが実装できたらレガシーコードをリファクタリングして改善していきます。 また、このレガシーコードはビジネスと直接紐付いているというシナリオの元、新規機能の追加実装も業務として入ってきます。 そのため、演習者はソフトウェアを破壊しないように注意しつつ、新規機能の実装やリファクタリングを行う必要が出てきます。 また講義では、レガシーコード改善の真の目的が「ユーザへの価値提供の速度を最大化する」であることに触れ、 これから演習者がどのような実装戦略を立ててユーザへの価値を最大化するかの意識付けを行いました。 Day4/Day5 ここでは具体的なリファクタリング方法や細かなTipsを説明しました。 なお新規機能の追加や新たなお題の出題は無く、演習者がこれまで出された要求に対して取り組む時間となります。 受講生の反応 ここからはデータプラットフォームサービス部の堀内が、研修の受講生の反応についてご紹介します。 研修に参加した動機 事前アンケートで研修に期待することを聞いたところ、下記のような回答が得られました。 レガシーコードに対して具体的にどう対処すれば良いか、どういう観点でリファクタリングすれば良いか学びたい。 テスト駆動開発と絡めてレガシーコードをリファクタリングする手法について学びたい。 業務でレガシーコードを多く目にするので、研修での学びをチーム内で展開するなど、それらを減らす取り組みをしたい。 また、新入社員の方が中心ということもあり、受講生のスキルは下記のような状況でした。 テストケースを考えたことや単体テストを実装したことはあるが、E2E などの結合テストは未経験という人が多い。 CI (Continuous Integration) を使ったことがある人は多いが、設定したことがある人は少ない。 GitHub を使ったことがない人や、ペアプログラミング / モブプログラミングをしたことがない人はそれぞれ半数程度。 研修中の様子 受講生は 2 人 1 組でチームを組んでもらい、そこにメンターが 1 人付くという形で演習に取り組んでもらいました。 研修中のコミュニケーションのために Slack を提供し、ペア作業ではチーム毎の Channel で Huddle を使いました。 Day1 のコードリーディングでは、準備した我々も「分かる」と言いたくなるようなコメントが多く上がりました。 コメント / ドキュメント / テストがなく、処理の内容が分からない。 変数名 / 関数名が分かりにくく、意図が読み取れない。 ソースコードのネストが深かったり、同じような処理が散らばっていて、追い掛けにくい。 Day2 では、レガシーコードに手を入れ始める前に、 まず単体テスト (pytest) と結合テスト (Postman) の実装をしてもらったのですが、 チームメイトと初対面の人も多い中、Visual Studio Live Share などを活用して上手くペアで作業を進めており、非常に驚きました。 Day3 〜 5 では業務に沿ったシナリオで演習に取り組んでもらったため、実際に直面しがちな問題に向き合ってもらいました。 各チームの振り返り (KPT) を覗いてみると、下記のようなコメントが見られました。 良かった点 レガシーコードの課題を GitHub の Issue に洗い出し、チームメイトとこまめに情報共有ができた。 テストコードを先に作り、それを通すように実装することで、テスト駆動開発を実践できた。 「ユーザに価値があるか?」を基準にして、機能追加とリファクタリングの優先順位を判断できた。 docker-compose と GitHub Actions を使って CI を実現できた。 悪かった点 機能追加を優先した結果、リファクタリングがおろそかになり、レガシーコードを生み出してしまった。 リファクタリングを後回しにした結果、デバッグのコストが増え、実装を完了できなかった。 Postman など、演習で利用するツールを使いこなすのに手間取ってしまった。 メンターからのアドバイスとしては、NTT Com の技術顧問でもある @t_wada さんの下記資料が共有されていました。 研修後の評価 5 日間の研修を終えた受講生に事後アンケートで「今回の研修で学んだ知識・経験は業務で活かせそうだと思いますか?」と聞いてみました。 どの質問も「そう思う」「非常にそう思う」と回答した人が 8 割以上で、メンターとしてはホッとする結果でした。 また、アンケートには下記のようなコメントが寄せられました。 実際の現場では様々なコードがあり、そこから作成者の意図などを読み解く必要があるので、今回の経験を活かしていきたいと思った。 テスト駆動開発を実践できたのは非常に得るものが多かった、日頃の開発でもテストを書いてから実装に取り組みたい。 リファクタリングをする上での観点 / 考え方や、機能追加との兼ね合いなど色々な側面での知見が得られたとので、今後の業務に反映していきたい。 講師側の所感 後日、講師/メンターをした社員で集って振り返りを行いました。 Keep 資料に重要なポイントが落とし込めていて分かりやすかった。 GitHub の Issue で進捗の見える化をした結果、研修準備のプロジェクトが大炎上しなかった。 (ラフではあるが) 事前に研修をテストプレイをして資料や題材をブラッシュアップできた。 Problem Day3 以降、受講者がデザインパターンやリファクタリングなどの知識不足によって遭難しはじめる様子が見て取れた。 講義資料がギリギリまで出来上がらなかったり、コードと資料の齟齬があったりした。 Git / GitHub や Postman の使い方に関する資料が足りていなかった。 研修題材にわかりにくい部分があった。 Try Git / GitHub 研修の資料を流用するなどして、事前学習をもう少し充実させたい。 @t_wada さんに受講してもらって模範回問を用意する、研修の流れや分量についてアドバイスをもらう。 今回の研修内容は全体的に難しめだったので、難易度の調整をする。 まとめ・今後について 今回の研修の評価 / 反省を受けて、2022 年も新入社員向けのソフトウェア研修を実施したいと思っています!!
アバター
はじめに はじめまして!情報セキュリティ部で社内バグバウンティ(NTT Com Bug Bounty Program)の運営をやっている長妻です。 この記事では、NTT Comで開催している社内バグバウンティを紹介します。また、社内バグバウンティに限らず、一般的なバグバウンティへ未経験の方にも参加してもらうために基本的なバグ調査環境を紹介します。 NTT Com Bug Bounty Programについては以下の記事もご確認ください。 社員が“バグハンター”として自社サービス・システムの堅牢化に参画 NTT Comが社内バグバウンティプログラムをスタート - Shines|NTTコミュニケーションズ そもそもバグバウンティとは? バグバウンティという言葉を聞いたことはあるでしょうか? 近頃はニュースにもされるようになってきたので、この記事の読者層は知っている人も少なくはないのかなと想像しています。 バグバウンティとは、企業などが自社のサービスや製品についてバグ調査案件を公開し、それを見たバグハンターにバグを調査・報告してもらう制度です。報告の対価としてバグハンターには報奨金が支払われます。 報告内容によっては報奨金が数百万円にもなることがあり、バグバウンティで生計を立てる人もいるとか。。。 企業としても脆弱性診断などでは見つからないようなバグが見つかる可能性があるため、多額の報奨金をかけてバグバウンティ制度を設けている会社もいるわけですね。 NTT Comでも、社内に閉じた社内バグバウンティを始めました。 社内バグバウンティでは社員に活躍してもらう 社内バグバウンティは、主にNTT Com社員(と一部のグループ会社の社員)にバグハンターとしてバグを探してもらおうという取り組みです。 社内システムのバグを、社員が見つけて報告するという構図で、もちろん報奨金も支払われます。 参加の際には、調査を募集するシステムも、調査を実施する社員も社内バグバウンティへ参加登録をしてもらいます。 特に調査に参加する社員には、調査対象システムへ高負荷をかける調査は行わない等の禁止事項や参加規約を読んで同意してもらっています。 参加登録が完了した社員は、業務としてではなく個人の活動としてバグハントを行います(もちろん、参加登録したからといって活動を強制されるということではありません)。 報奨金が支払われるのは報告内容が脆弱性に認定された場合のみですが、社員はお金目的だけでなく、スキルアップ、力試し、品質向上への貢献、といったいろいろな目的で参加してくれています。 オープンなバグバウンティ制度では多くのバグハンターへアプローチできる一方で、相手の身元が分からないなどのリスクがあります。(例えば、相手が反社会的勢力で、報奨金が利益供与にあたらないか?など) 社内バグバウンティでは信頼のおける社員(と一部のグループ会社の社員)に絞ったバグハンターの募集となるため、システム運用担当者としても安心して参加できる制度です。 バグバウンティの入門として 社内バグバウンティは一般的なバグバウンティと比べると報奨金は控え目ですが、一方で社外にオープンではないためライバルが少ないです。 熟練者がひしめき合うようなオープンなバグバウンティプログラムと比べると、初参加でもチャンスがあるかもしれません。 少額でも良いから調査してバグを見つけて達成感を味わいたい人に向いている制度かなと思います。 また、多くの貢献をしてくれたバグハンターの方は、『Hall of Fame』として社内バグバウンティのオフィシャルページに掲載されるので、ぜひチャレンジしてみてください。 バグバウンティをこれから始める方のために ここからはバグ調査で使う基本的なツールの説明を行います。ローカルプロキシを使ってリクエストの改ざんが行える方は、これ以降の文章は読み飛ばしてもらって大丈夫です。 調査環境のセットアップ ここからは以下の調査環境をセットアップするまでについて説明していきます。 ローカルプロキシ:Burp Suite Community Edition 有名なローカルプロキシツールです。WEBリクエストの改ざんや複製など様々な操作を行えます。 https://portswigger.net/burp/communitydownload WEBブラウザ:Firefox & FoxyProxy Standardアドオン FoxyProxyは、Firefoxのプロキシ転送設定を簡単にしてくれるアドオンです。 https://addons.mozilla.org/ja/firefox/addon/foxyproxy-standard/ Burp Suite Community EditionのProxyサービスの設定 まずはBurp Suite Community Edition(以下、Burp)をインストールして、Proxyサービスの設定を見てみます。 下の画像のようにデフォルトでは 127.0.0.1:8080 にてポートを開放して待ち受けています。この画面から細かい設定も可能ですが、このままデフォルト設定で説明を続けます。 Firefoxのプロキシ転送設定 次にFoxyProxy Starndardのアドオンをインストールしたら、Burpのローカルプロキシへの転送設定を行います。 アドオンが適用されると、下図のようにブラウザの右上にアイコンが表示されます。 続いて、 アイコンをクリック > Options > Addと遷移して、プロキシ設定を登録します。 Burpのプロキシは 127.0.0.1:8080 で立ち上がっていたので、下図のように埋めたらSaveします。 設定の登録が完了すると、下図のようにブラウザの右上からどのプロキシ設定を適用するかを選択できるようになります。 作成したプロキシ設定にチェックマークを付けたら、指定したプロキシを経由するようになります。 そこで、 https://example.com へアクセスしてみると、、、 上図のように警告が表示されました。これはhttps通信をする際にプロキシを経由させたため、中間者攻撃とブラウザに判断されてセキュリティ機能が働いたためです。 FirefoxにBurpのルート証明書をインポートする 続いて、Burpのルート証明書をブラウザに読み込ませて、こうした警告が出ないようにします。 証明書は以下のBurpの「Proxy > Options」画面から、「import/export CA certificate」ボタンを押すことで取得できます。 取得したルート証明書をFirefoxにインポートします。 Firefoxの設定画面を開いたら、「証明書」と検索して証明書マネージャーを開きます。 先ほど取得したルート証明書をインポートしてください。インポートが完了すると、「PortSwigger CA」が追加されます。 ここまでやれば設定は完了です。再び https://example.com へアクセスして確認してみます。 ローカルプロキシを介してアクセスできました。 「Intercept」や「Repeater」といった機能を使ってリクエストを改ざんできます。 あとはバグバウンティプログラムへ参加し、ルールを守ったうえで調査に参加してみてください。
アバター
はじめに こんにちは、データプラットフォームサービス部で IoT 系サービスやフル MVNO 基盤、ローカル 5G サービスの設計開発を担当している真山です。 本ブログでは過去に IoT Connect Gateway を使ってみた 第3回 〜観葉植物育成状況の可視化〜 について投稿しています。今回は、当社のローカル5Gのサービス紹介だけでなく、ユースケースや周辺技術、検証実験の内容の一部を発信していきたいと思っています。また皆様からのフィードバックを次期開発に活かしたいと考えていますので、リクエストやコメントを頂けますと幸いです。 そもそも、ローカル 5G とは? ローカル 5G は携帯電話事業者による全国向け 5G サービスとは別に、地域の企業や自治体等が個別に利用できる 5G ネットワークのことです。地域・産業のニーズに応じて、自らの建物内や敷地内などの特定のエリアにおいて自営の 5G ネットワークを構築・利用・運用できます。利用には国で指定された無線局免許の取得が必要であり、自ら取得することも、免許取得した他社のシステムを利用することも可能です。利用できる周波数帯は、Sub6 (n79) と呼ばれる 4.6~4.9 GHz と、ミリ波 (n257) と呼ばれる 28.2~29.1 GHz です。 NTT Communications のローカル 5G とは? NTT Communications はローカル 5G サービスを2021年3月30日にリリースしました。 www.ntt.com www.ntt.com NTT Com および NTT グループの知見を活かし、ローカル 5G の利用に必要なプロセス (導入コンサルティング、免許取得、機器構築、運用の支援) をワンストップで提供します。 ローカル 5G サービスの特徴 1. NTT ドコモの無線技術・ノウハウを活用し、高信頼のサービスを提供 ローカル 5G 導入前のエリア調査や回線設計、設置工事、運用の一部などにおいて、無線技術の知見を持つ 株式会社 NTT ドコモのローカル 5G 構築支援サービス を活用します。NTT グループのアセットを活用することで、お客様の導入に際する障壁を低減したうえで信頼性の高いローカル 5G 環境を提供します。 2. 5G 無線基地局装置と 5G コアを必要な期間のみ利用可能 Sub6 帯に対応したローカル 5G の機器類一式を NTT Com から一元的に提供します。また、ローカル 5G の環境構築において 5G コアおよび無線基地局設備を月額利用型のサービスとして提供しています (お買い上げも可能)。これにより、お客さまの初期コストを低減しながら、お客さまの技術検証などでの必要な期間のみローカル 5G の利用が可能となります。最低利用期間は 1 年間となります。 3. Smart Data Platform とのワンストップ提供でデータ利活用環境を実現 NTT Com の Smart Data Platform (SDPF) と一体的に提供可能です。専門的な知識や技術、手続きが必要となるローカル 5G システムの環境構築だけでなく、データ利活用に必要なデータ収集・蓄積・分析などの機能をワンストップで提供し、お客さまの DX を推進します。SDPF と組み合わせたユースケースについても今後ブログのなかで発信したいと思います。 NTT Com のローカル 5G での強み 1. クラウドコア型構成 ローカル 5G の提供体型としてオンプレ型が多い中、NTT Com の提供するローカル 5G サービスは現在クラウドコア型のアーキテクチャを採用しています。5G の制御を司る各種コア装置をクラウド上に配置し、お客様拠点に配置される無線基地局設備 (RAN 装置) と分離しています。5G コアではアクセスとモビリティ管理を担う AMF やセッション管理を担う SMF などのネットワーク制御 (C-Plane) 機能を提供します。また 5G コアのユーザーデータ処理 (U-Plane) 機能部である UPF についてはお客様拠点内に配置することでお客様ネットワークまでの伝送距離を短縮し、低遅延な通信を実現します。C-Plane と U-Plane を分離し、C-Plane をクラウドに集約することで導入時や運用時のお客様の負担を軽減します。 2. 同一 5G コア・複数拠点構成 お客様のご要望によっては、複数拠点でのローカル 5G サービスの利用を希望されるケースがあるとか思います。このようなご要望については拠点を増やす毎に新たに 5G コアをご契約いただくかなくとも、クラウドコア型のメリットを活かし、同一 5G コアを利用しながらも複数拠点に RAN 設備を配置してローカル 5G をご利用いただくことが可能です。アドレス設計や 5G 端末挙動についてもサポートします。 3. 高セキュリティを実現する閉域の中継ネットワーク クラウド上の 5G コアと RAN 装置の間の接続には、 NTT Com の閉域ネットワークサービスである Arcstar Universal One と Flexible InterConnect (FIC) を中継回線として利用し、ローカル 5G に求められる高いセキュリティをネットワークレイヤでも実現しています。5G コアおよび中継回線におけるお客様拠点の RAN 設備から 5G コアへの制御系信号用ネットワークはお客様専用としてご利用いただきます。 最後に ローカル 5G は黎明期でありサービス構築だけでなく、様々なユースケースの創造も我々のミッションと考えています。ローカル 5G の無線基盤としての新機能開発・提供だけでなく、従来技術では解決が困難であったお客様の課題をローカル 5G を用いることでいかに解決できるのか?といった観点のもと、様々なアプリケーションとの組み合わせによる相乗効果を検討していきます。この内容についても今後ブログの中でご紹介できればと思います。また、ローカル 5G を使って試してみたい機能やソリューション、アイディアがあればぜひご連絡をいただけると幸いです。 次回予告 NTT Com の本社で構築したローカル 5G の無線環境調査の検証内容について紹介させていただきます。 ローカル 5G サービス関するお問い合わせ先 ローカル 5G に関するお問い合わせ お問い合わせフォーム メールでのお問い合わせ l5g-support@ntt.com ※お手数ですが@を半角文字に置き換えてください
アバター
はじめまして。プラットフォームサービス本部セキュリティサービス部門の大倉です。普段はインシデントレスポンスチームに所属し、お客様で発生したセキュリティインシデントの調査を業務として行っています。 今回、2021年12月4日(土)に開催された TechWorkshop「NTTコムのセキュリティ業務紹介&サイバー攻撃対応ワークショップ」の内容について紹介します。 TechWorkshopについて 各技術分野のプロフェッショナル社員がお届けする、当社の最先端技術を体感し、また身に付けることができるワークショップ形式のプログラムです。 ―― イベント紹介(採用情報) より抜粋 過去にはこのようなワークショップを開催しました。 コーディング体験 ISPネットワーク構築・運用体験 Kubernetesハンズオン CI/CDハンズオン いくつかのTechworkshopイベントは、当日の様子をブログとして公開しています。ご興味がある方はこちらのブログ記事もご覧ください。 TechWorkshop 「現場のエンジニアと一緒に解く!コーディング体験」を開催しました! TechWorkshop「プロのネットワークエンジニアと学ぶ!ISPネットワークのつくりかた」を開催しました! どのワークショップも各分野を主な業務にしている/業務に使っている社員が講師となり、かつ自分たちでイベントの企画・運営をしています。今後もいくつか開催予定ですので、興味のある方は TechWorkshopのページ をチェックしていただき( NTT Com公式Twitter でもご案内します。)、ぜひ参加申込をお願いします。 「NTTコムのセキュリティ業務紹介&サイバー攻撃対応ワークショップ」とは? 当日の大まかなスケジュールは以下となっていました。午前は業務紹介がメインとなっており、午後から演習を行いました。 午前 会社紹介 NTTグループとは? NTTコミュニケーションズってどんな会社? NTTコミュニケーションズ×セキュリティ セキュリティ関連部署の業務紹介 NTTコミュニケーションズ内の各セキュリティ部署(CSIRT、研究開発など)の業務 NTTセキュリティ・ジャパンの業務 エヌ・エフ・ラボラトリーズの業務 午後 演習 問題説明、演習環境へアクセスする方法の説明 演習開始 各チームに分かれて攻撃の解析 発見した攻撃に応じて、サーバの設定変更・脆弱性の修正 発見した攻撃内容の発表・答え合わせ 実際に行われた攻撃を解説 各チーム毎に発見した攻撃を発表 懇親会 午前の部 : 会社紹介 & セキュリティ関連部署の業務紹介 ワークショップの午前は、NTTコミュニケーションズの会社説明、および、各セキュリティ部門の社員から実際の業務内容の紹介しました。当日は、NTTコミュニケーションズ所属の社員だけでなく、NTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズの社員も参加していたため、多様なセキュリティ業務を紹介できました。それぞれの業務ごとに異なる魅力がありますが、入社したあとどのような仕事があるのか、参加者の皆さんもイメージしやすくなったことと思います。 午後の部 : サイバー攻撃対応ワークショップ 演習概要 本演習の目的は、「セキュリティエンジニアの仕事を体験する」ことです。インシデントレスポンスの流れを基に、以下を経験してもらいます。 システム環境に対するサイバー攻撃を検知したり、対処したりしてもらい、セキュリティオペレーション業務を体験する。 システム環境に存在する脆弱性の発見・修正をすることで脆弱性診断、ペネトレーションテスト業務の一部を体験する。 本演習は、グループ会社のエヌ・エフ・ラボラトリーズより提供いただいたコンテンツを利用しています。 まず、演習のはじめにルールや見るべきログなどを説明しました。その後、演習環境へのアクセス方法や接続確認を行いました。 演習では参加者4名ほどでグループを作り、クラウド(AWS)上に構築された演習環境へアクセスしてもらいます。演習環境では、Webサイトがホストされているサーバが存在しており、そのサーバに対してインターネットを想定したネットワークから様々な通信が行われています。参加者の皆さんにはWebサイトの可用性を維持しつつ、時間内に行われる多くの攻撃を防いでもらいました。 本イベントでは、現在のシステム状況を把握しやすいように、スコアを別途表示していました。今回はコンテストではないため、順位を争うものではありませんでしたが、自分たちの設定が想定どおりに反映されているかどうかの判断に使ってもらいました。参加者は、行った対処が正しい方向であればより高得点が得られ、一方、アクセス断等誤った対処をした場合加点されない、といったフィードバックを得られました。 演習中の様子 演習の詳細は、本Workshopを再度開催する予定であるため書くことはできないのですが、サーバに記録されていく多種多様なログ(認証ログや動作しているサービスのアクセスログ、FTPログ、IDSのアラートログ等)を解析し、どのような攻撃がきているのかを確認してもらいます。さらに、稼働しているサービスに脆弱性が存在しているため、ソースコードを読むことも求められます。普段見たことがないログや扱ったことのない言語に戸惑う人が多かったようですが、自分たちでログの仕様や言語を確認してもらいました。 攻撃の痕跡や脆弱性を発見した後は、その対応策の実施が必要になります。設定の変更やソースコードの変更などをしてもらうことになりますが、思い切った変更をするチームが多かった印象です。そのため、変更内容によっては予期しないサービス停止、アクセス断が発生する場合もあります。当日も演習中にSSHの設定を変更しサーバに接続できなくなりそうなトラブルや、バックアップをとっていなかったためソースコードを改変した後、元に戻せなかったチーム等がありました。実際に運用する際に注意深く対応しないと、二次被害が発生するという良い教訓になったことでしょう。なお、設定ミス等によりサーバにログインできなくなるなど、どうしようもないお手上げ状態になってしまった場合は、対象サーバの状態をリセットする救済措置も行いました。 その他、参加者の様子を見守っていましたが、チームで通話を繋ぎっぱなしにして細かくタスクを確認するチームがいる一方で、細かく相談はせずに見つけた攻撃を淡々と共有するチームもあり、各チームそれぞれ特色が見られ面白かったです。また、チーム内で教え合いや議論が行われている場面もありました。 攻撃の解説・発見した攻撃内容の発表 演習の終了後には攻撃内容の解説をしました。参加者の中には、見逃してしまった痕跡や攻撃を知り、感心や悔しがる様子が見受けられ、準備したかいがありました。 その後、各チーム毎に発見した攻撃を発表してもらいました。ログやコマンド結果に対して詳細に注釈を入れて示すチームや、自分たちが見つけた脆弱性とその対応状況を表に分かりやすくまとめるチームなど、短時間で伝わりやすいスライドを作成しており驚きました。 なお、我々が用意していた攻撃すべてを発見・対応したチームは残念ながら存在しませんでした。演習の時間が3時間程度となっており、攻撃の種類も存分に楽しんでもらえるように盛り盛りにしていたため、難しかったと思います。すべての攻撃を発見するには、個人の技術力も重要ですが、チーム力も必要です。実際のインシデント対応では、関係各所との調整やタスクの割り振りなども必要になります。チームで行っていることを最大限活かして力を発揮しなければいけないことを実感いただけたことでしょう。 参加者の声 Workshop後に参加者の方からいただいたアンケートでは、多くの方に「大変良かった」「いろいろ学ぶことができた」などのコメントを多くいただき、とても嬉しく思いました。以下はコメントの一部抜粋になります。 攻撃を特定するためにどのログを見なければならないのかを学ぶことができた。 ログの解析だけでなく、実際に手を動かしてその対策を自分たちで実施するという流れで大変勉強になった。 チームで作業をするというのが最近なかったので、メンバと話し合いながら作業をするのが楽しかった。 さらにイベントの内容だけでなく、質問対応などから職場や社員の雰囲気を感じ取ることができた方も多かったようで、ポジティブなコメントをいただきました。オンラインのイベントだったため、コミュニケーション不足などこちらにも至らない点が多かったと思いますが、参加者の皆さん、ありがとうございました。 おわりに この記事では、2021年12月4日(土)に開催された TechWorkshop「NTT Comのセキュリティ業務紹介&サイバー攻撃対応ワークショップ」について紹介しました。昨年も似た内容でイベントを企画しましたが、今年はさらに多くの方に参加していただきました。こういった攻撃対応を経験することは少ないかと思いますので、業務をイメージできる良い機会になったのではないでしょうか。 我々社員としても、NTT Comならびにセキュリティ業界に興味を持っていただけたなら幸いです。 ちなみに 同じ内容のイベント を2022年2月19日(土)に開催予定です。応募締め切りは、2022年2月4日(金)23:59までとなっておりますので、この記事を読んで興味を持った方はぜひご応募ください!
アバター
はじめに 初めまして!イノベーションセンターで ノーコードAI開発ツール「Node-AI」 のプロダクトオーナーやXAI・因果分析の研究をしております、切通恵介( @kirikei )です。 Node-AIは2021年10月11日にリリースされたNTT Communicationsの内製開発サービスで、その名の通りブラウザ上からノーコードでAIモデルを開発できるサービスで、製造業のお客様を中心に異常検知やプラント運転支援などの様々な領域で活用されています。(ニュースリリースは こちら や こちら や こちら ) いつもはサービスの営業的な紹介をすることが多いのですが、今回はEngineer's Blogでの執筆ということで、エンジニアの方向けの技術、プロダクトマネジメント、チームビルディング、スクラムなどの様々な観点でお伝えできればと考えています。とはいえ、Node-AIに関しては詳細に書きたいことが山ほどあって1つの記事には到底収まらない(!)ので、何回かに分けてシリーズモノとしてお送りする予定です。 そんな中で記念すべき第1回の今回の記事では Node-AIがどのようなサービスであるか Node-AIで扱う技術領域にはどのようなものが含まれるか Node-AIをどのように開発しているか の3点に関してをプロダクトオーナーである私とテックリードである内藤理大、スクラムマスターの小澤暖から述べます。 Node-AIとは Node-AIはノーコードAI開発ツールというその名の通り、ブラウザから以下の図のようにカード(モジュール)を直感的につなげるだけで時系列データの前処理からAIモデルの学習・評価までの一連のパイプラインを作成・実行できるツールです。 Node-AIで目指す世界 Node-AIチームには「 データ分析にまつわる全て(機能・人)が集まってコミュニケーションしながらデータ分析を継続的に実行し様々な課題を解決し続けている世界 」を目指す、というビジョンがあります。 少しこのビジョンを掘り下げてみます。 【課題解決に必要なもの】 昨今、AIという言葉の流行によって様々なデータ分析案件が生まれ、課題解決に利用する流れができてきた一方で「うまく進まない」例もたくさんあります。私自身データサイエンティスト的な役割、すなわちお客様から課題をヒアリングし、データを受け取りそれを分析して最終的な課題解決を行うといういわゆる「データ分析案件」に携わる中で様々な失敗してきています。 この原因としては、「 課題解決 」というものの範囲が広く、そして複雑であることが挙げられます。例えば以下のような案件があったとしましょう。 データ分析者な我々にとってはまずプラントに存在する時系列のセンサーデータをcsvなどでもらって、その中の不純物(シリカ)の量を表すカラムを目的変数として選択し、その未来の値を教師データとしてcsvのデータから特徴量を作成して何かしらのモデル(ARやLightGBMでもいいしTransformerのような高級なモデルを使ってもいいし...)で予測すれば良い、というのが頭に思い浮かびます。 しかしながら、このモデルを作成するという部分が課題解決自体を示すわけではありません。このゴールは少なくとも「 精度の高い予測値をリアルタイムに可視化し、その結果を運用者が見て攪拌のための操作を変えて鉄の収集量が上がること 」です。つまりモデルを作成した後に業務に導入するところまで必要となります。 そして「少なくとも」とした通り、このゴールも残念ながら完全ではありません。例えば「精度の高い」はどれくらいの精度なのか、「予測値」とはどれだけ先の未来を当てるのか、「リアルタイム」とはどれくらいの遅延が許されるのか、「運用者が見て」とはどのようなシステムでどんなUIで見るのかなどを課題に関わる様々な ステークホルダーとすり合わせ なければなりません。 さらに、上記の具体的なゴールを初めに決めたとしてもステークホルダーとの話し合いは終わるのではありません。例えば特徴量の作成には実際にデータを見ている 現場の運用者 の知見が必要になります。さらに十分な精度を得たとしても実際に導入できるかどうかは最終的なGoサインを出す 意思決定者 との話し合いが必要になりますし、最終的にモデルを何らかのシステム、例えば工場であれば工場で既に使われているデータ可視化ツールに組み込むためには 工場のシステム担当 とも意思疎通を図る必要があります。 そして、導入を行ったとしてもそこでプロジェクトが終わることはありません。例えば季節変化による精度の劣化への対応や、ビジネス要件やデータ定義の変化によるモデルの再作成や問題の再定義など、ステークホルダーとコミュニケーションしながら改善を進めていく必要があります。 長々と書きましたが、ここまでまとめると、以下となります。 データ分析による課題解決を継続的に行うには、モデルを作成して終わりではなく、意思決定やシステム導入に至るまでが必要。またデータ分析者で完結することはなく、課題に関係するステークホルダーとのすり合わせ・相互理解が重要。 実際にこれらが実現できているかというと、そこには様々な壁があります。 【 壁その1:ステークホルダーの分析内容の相互理解 】 上で述べたとおり、課題解決にはステークホルダーとのすり合わせ、特に分析中は精度を上げたり導入判断を行うためのフィードバックが重要になります。この時、 データ分析者とステークホルダーは分析内容をしっかり把握することで、より深いフィードバックが可能になります 。 例えば、時系列データであれば時系列の前処理において時間窓をどれくらい取るのか、サンプリング間隔をどれくらいにするのかを運用者の知見から決めることがあるでしょうし、特徴選択においては運用者が普段不純物の状態を確かめるのに必要なセンサーを教えてもらうこともあります。このような精度を上げるためのフィードバックには、専門家にもどのような前処理や学習を行っていてどのようなパラメータがあるのかを詳細に理解してもらう必要があります。 また、その理解は意思決定者にも必要になります。意思決定者は導入のための責任を負っているので、その分析によって真に課題が解決でき、自分や現場の知見と照らし合わせても問題ないかどうかを確かめる必要があります。この時に内部のフローを理解していることで、意思決定に必要な情報が得られるため、正当な判断をしやすくなります。導入を担当する社内のシステム担当者にとっても、現状運用を行なっている可視化システムに繋ぐためにデータのフローを理解するのは非常に重要です。 一方で、データ分析者の分析の多くはPythonやRなどのプログラミング言語でなされており、専門外であることも多い ステークホルダーにとってはその分析自体がブラックボックス として捉えられてしまいます。この状態では上記で挙げたような精度を上げるための深いフィードバックや、もっと手前の課題設定が正当性の把握などが遅れてしまい、精度が上がるのに時間がかかったり、導入に至らず課題設定からやり直したりするようなことが起きてしまいます。 【 壁その2:コミュニケーションの重さ 】 ステークホルダーとのすり合わせを行うために、データ分析者とステークホルダーで例えば2週間に1回打ち合わせをすることがあるでしょう(ステークホルダーは大体忙しいので意思決定者が御登場するのは1ヶ月に1回かもしれない)。この時データ分析者は打ち合わせのために自分の統計量の可視化結果や予測値と実測値のグラフ、はたまた特徴量の設計方法などをJupyterLabのスクショをとってスライドにぺたぺた貼って、さらにステークホルダーにも分かるように平易な言葉で説明を並べ、2週間空いているので課題やKPIも改めて確認して...などの報告書を作成します。これはデータ分析者にとっては重労働で、 本来は分析に利用したい稼働の大半を報告書作りに割いてしまう こととなります。 さらに打ち合わせの中では月一参加の意思決定者から課題設定が正当でないことを指摘されてしまったり、現場の運用者から新しい知見が与えられて前処理を追加しなければならない、といったことが起きます。これによりデータ分析者の2週間の稼働は無駄になってしまうことも多々あります。打ち合わせの間隔を1週間にすればこれも防げるように思えますが、先に述べた報告資料の重さを考えると2週間程度のスパンは必要ですし、まさにデッドロック状態になってしまいます。このように コミュニケーションの重さからフィードバックの間隔が空いてしまうことにより、最終的な課題解決のための右往左往が増えてしまうことになります。 そうやっている間に投資判断を行う人から継続をNGにされてしまうこともあるでしょう。 【 壁その3:導入の判断と導入自体の難しさ 】 上記の壁をなんとか乗り越え、分析がある程度進みモデルが作成できたとしましょう。ここからは導入に向けた壁を乗り越える必要があります。 導入には意思決定者のGoサインが必要になります。これを判断する際問題になるのが「モデルの信頼性」です。例えば1時間後の不純物の割合の予測によると、不純物が減る兆候を示しており、それをもとに運用者が攪拌のスピードを上げるとしましょう。もしこのモデルの予測が誤っていた場合、攪拌のスピードアップによってプラントが傷ついてしまい生産がストップする...と言うこともあり得ます。これを防ぐためには様々な方法がありますが、その1つとしてモデルが何を見て予測をしているかを明らかにし、それが現場で知られている知見と整合性が取れるか確かめるというものがあります。運用者の間では攪拌の際温度を見ながら操作することが提唱されていると言うような知見があれば、モデルが温度を入力として重要視しているかを確かめることで、そのモデルの信頼性を測ることができます。一方、 昨今利用される非線形性を含む複雑なモデルではそのような予測の判断根拠を抽出するのは難しく、さらに抽出したとしても説明自体を解釈するにはより専門的な知識が必要である と言う問題があります(この分野はXAIと呼ばれ、私が専門としている分野で色々と語りたいですが、ここでは割愛)。 また、モデルが仮にTensorflowで用いられるSavedModel形式で保存されていたり、前処理がsklearnのpreprocessingで行われているとしましょう。このモデルの導入や運用を行う工場のシステム担当者にとっては、 作成されたモデルを扱うにはPythonといった言語や機械学習ライブラリ、Webアプリケーションの知識が必要であり、かなりハードルが高い ものとなります。したがって、技術的な観点でも現場にモデルを導入していくことは課題があります。 Node-AIの解決策 ここまで長々と課題を述べてきましたが、ここからはそういった課題を解決するためにNode-AIがどのような特徴を持っているのか、説明していきたいと思います。 Node-AIが他のツールと比べて最も異なるのは、上に挙げたような「ステークホルダーとのコラボレーション」を重要視している点です。 今までのツールのようにデータ分析者のみが分析を効率化するのに使うのではなく、ステークホルダーも含めて利用することでデータ分析での課題解決全体を効率化することを目的 とした一風変わった特徴を持っています。すなわち、最終的な課題解決のために必要不可欠な分析の理解のためのコミュニケーションや、導入判断などを打ち合わせだけではなく、ブラウザからアクセスするNode-AIの上で全部やってしまおうと言うアイデアです。したがって、単なる機械学習モデルを作成するツールではなく、 データ分析プロジェクトを効率化するツール と言う側面も持っています。以下の項ではそれをどのように実現しているか、いわゆるHowな部分を紐解いていきます。 【 ビジュアルプログラミング 】 Node-AIは上記の画像の通り、モジュール(我々はカードと呼んでいます)をドラッグ&ドロップでつなげることでデータの前処理、モデルの定義、学習、評価といった一連の分析のフローを実現します。また、各カード内で処理されたデータやその統計量はカードを開くことで逐一確認できるようになっています。これにより、プログラミングの知識のないユーザでも機械学習モデルの作成を行うことができるだけでなく、 分析フローが可視化される と言うメリットがあります。 これにより、課題の1つのステークホルダーとの分析内容の相互理解が促進され、 データ分析者だけでなくステークホルダーにとってもどのように分析しているかが明確になり、分析工程自体がブラックボックスになることを防ぎます。 意思決定者にとってはどのような分析が行われているか知ることで分析の信頼性を得ることが容易になりますし、データの専門家にとっても具体的なパラメータに対するアドバイスがやりやすくなります。加えて、データ分析者にとっても、複数人で分析する場合にその分析フローを引き継いだり比較しやすくなると言うメリットもあります。 【 コラボレーション機能 】 報告資料作成やフィードバックが早くもらえないというコミュニケーションの重さも課題解決の壁の1つでした。 Node-AIでは可視化された分析フローの横でMarkdown形式による資料作成 ができます。今まではスライドに可視化した画像を貼り付けたり、前処理の中身を書いたりと、報告書を作成するのに時間がかかっていましたが、可視化すべきデータや分析フローはNode-AI上に全てあるので資料作成のためにデータを取り出す必要もありません。さらにデータの全容もその場で確認できるため、より深い議論が可能になります。 また、フィードバックに関しても、 コメント機能を利用することでステークホルダーにメンションを飛ばして質問できます 。メンションされたコメントはユーザにメールで通知され、ユーザはそのコメントを見に行くことができます。これにより分析中に出てくる相談をクイックに行うことで、打ち合わせのみと比べて効率的な分析を行えます。 ちなみにNode-AIの分析フローを作成する場所をキャンバスと呼んでいて、文字を書いたり分析フローを作ったりできるまるで自由帳のような働きができるようにしています。 【要因分析・因果分析機能】 導入の難しさの原因の1つは機械学習モデルがブラックボックスであることです。この対応のためにNode-AIでは要因分析、いわゆるモデルの説明性を可視化する機能を備えています。この機能では予測に対してどの特徴のどの遅れ時間のデータが効いていたのかを重要度としてヒートマップで表示できます。さらに、ニューラルネットワークを利用した場合は各時刻における重要度の変化を閲覧することもできます。 さらに、特徴間の時系列を考慮した因果関係を可視化する因果分析機能を兼ね備えており、特徴量の選択やシステムの理解に役立ちます。例えば制御を意味する特徴が入っているデータの場合にはその特徴を変化させるとどういった結果が得られるのかなど、実応用の考察に必要な情報を与えることができます。 【開発したモデルを他システムに組み込みやすくする機能】 モデルの利用に対して機械学習の知識が必要になることから生じるモデル利用の困難さに対しては、Node-AI Berryというモデルを簡単に利用するためのライブラリを用意しています。このライブラリはNode-AIからダウンロードしたモデルを利用して、データを受け取って前処理からモデルの推論までを行いその結果を返すAPIを簡単に作成できます。具体的にはNode-AIからモデルファイルをダウンロードし、所定のフォルダにおいた状態で docker run するだけです。 さらに、学習データを与えることでそのモデルを再学習するような機能もついているので、課題解決の先にあるモデルの運用という部分もカバーしています。 小括 ここまでデータ分析による課題解決の困難さ・難しさを述べてきました。また、それをNode-AIでどのように解決しようとしているかを紹介しました。 世の中で様々なデータ分析案件が現れ、そして失敗していくのを見てきました(し、自分たちでも失敗してきた)が、一方で私たちはデータ分析の導入は既存のビジネスに絶大な効果を齎すことを信じています。実際に様々なデータ分析案件が真に課題を解決し、色々なところで成果を出し始めています。この流れを加速させるための道具として私たちはNode-AIというサービスを開発しています。(すっごい雑にいうと、もっと全員でデータ分析で色々解決しようぜ!って感じです。) Node-AIで扱う技術領域 本章の紹介は、 Node-AI のテックリードをしております、内藤理大(github.com/ridai)から紹介させていただきます。 Node-AIの開発では、コンテナ技術からアプリケーションの設計・実装まで、幅広い技術を検証した上で利用しています。 本章では、簡単に扱っている技術を紹介したいと思います。(各技術の中で苦労した話や工夫した話は後日、別記事にて紹介させていただきますので、よろしければそちらもご覧ください。) Infra技術  Node-AIは「機械学習を取り扱う」という性質上、GPUを備えたサーバもdeploy先環境として取り扱っています。具体的には、NTT ComAIインフラPJが提供するオンプレミスのGPUクラスタ環境と、GCPのComputeEngine環境を利用しています。(AWSやAzureでの構築も検討中)  また、開発環境と本番環境をコードで管理し、同一の環境を簡単に構築するためにも、アプリケーション自体はDockerコンテナ上に構築しています。  そして、アプリケーションを構成する複数のコンテナの管理を楽にするために、docker-composeを利用するパターンと、より複雑なオーケストレーションを行うためにKubernetesを利用するパターンを用意しています。 CI/CD/テスト  Node-AIでは、開発プロセス中のテストやdeploy作業の自動化にも取り組んでいます。テストについては、開発したPythonモジュールの機能テストのほか、RobotFrameworkを用いたエンドツーエンドテストを用意しています。  まず、テストの流れとしては開発した機能をGithubの作業ブランチにpushするたびに、CircleCIまたはGithubActionsを使った機能の単体テストが自動で行われます。また、本番環境にdeployを行う際には、NTT ComプロダクトのQmonusを利用して機能テストとエンドツーエンドテストが自動で行われます。  次に、deployの流れとしては、同様にQmonusを利用し、ターゲットのVM環境にアプリケーションを自動で配信し、立ち上げを行います。 コード管理  Node-AIでは、Gitを用いてアプリケーションのソースコードのリビジョン管理を行なっています。そして、それらコードのリポジトリとして、GitHubを利用しています。 UI/UX  Node-AIのフロントエンドは、Vue.js + Typescriptを用いたSinglePageApplicationがベースとなっています。ユーザの入力やサーバでの処理結果を動的に処理し、単一のページ上で軽快に機械学習が実行できるUXを提供しています。  また、UIについても、ElementUIを用いたデザインの統一と、SCSSを用いたスタイル指定の効率化に取り組んでいます。 Webアプリ開発  機械学習ではPythonでのライブラリ開発が活発であることから、Node-AI開発でもPythonを採用した開発しています。そして、Webアプリケーションフレームワークとしては、django/dinago REST frameworkを採用しています。  また、機械学習は非常に処理時間がかかるため、celeryというタスクのキューイング処理フレームワーク採用して、非同期的に学習が実行できる仕組みにしています。  他にも、実行に関わる永続データの保持には、MySQLなどのRDBMSの他、ObjectStorageを採用しています。これらデータの保存サービスとして、MySQLやMinIOをDockerコンテナとして立ち上げて利用できるほか、GCPのSaaSサービス等も利用できるような仕組みとなっています。  開発チームでは、これら仕組みを効率的に実装・実現させるため、DDDという開発手法とCleanArchitectureという設計思想を用いています。 機械学習  Node-AIでは、数値の時系列データをターゲットとした、異常検知と予測モデルの作成を可能としています。そこで用いる学習処理としては、sk-learnを用いた線形回帰処理と、tensorflowを用いたNeuralNetworkモデルの回帰/自己符号化器処理となります。  しかし、もっとも特徴的な機能は、「要因分析の可視化」や「因果分析」といったComオリジナルの技術にあります。この機能は、Comの機械学習の研究者による成果であり、Node-AIの開発者と共同で研究技術の導入・提供を行なうことができる体制になっているため、実現できています。  他にも、tensorflowを用いた処理では、optunaを用いたハイパーパラメータの自動探索機能とGPUリソースを判別して利用する仕組みを導入しています。また、mlflowを用いて、ユーザの学習処理の記録も保持しているため、結果の比較・検討も容易にできる仕組みになっています。 Node-AIのアジャイル内製開発 Node-AIチームのスクラムマスター(最近は社内のアジャイル開発案件支援をしながら社内へのアジャイル開発普及活動も)やってます小澤です。ここではNode-AIチームが日々どのように開発業務を行っているかご紹介したいと思います。 Node-AIの扱うAI(機械学習)やデータ分析といった分野は、最新のアルゴリズムは日々更新されお客様の求めるニーズも変化しやすい等、変化が激しく不確実性の高い領域です。これに対応するため、Node-AI開発では初期からアジャイル開発手法の1つである「スクラム」という開発フレームワーク(下図参照、詳細な説明は割愛します)を採用することでアジャイルに内製開発を進めています。 【スクラムフレームワーク】 チームメンバー構成としては、 プロダクトオーナー(認定資格(CSPO)) 1名 スクラムマスター(認定資格(A-CSM)) 1名 開発者(フロントエンド、バックエンド、QA等) 9名 デザイナー 2名 となっています。スクラムに関して外部認定資格を持ったメンバーもおり、それぞれ専門的な知識とスキルを持った10名程度の少数精鋭メンバーでチームが構成されています。 【スクラムチーム】 またスクラムは「スプリント」と呼ばれる通常1〜4週間といった比較的短期間の固定の時間枠(タイムボックス)を定め、この中で優先度の高い機能から順次開発したり、様々なスクラムイベント(後述)などを行っていきます(短期間のスプリントを何度も繰り返すことで変化する顧客や市場のニーズに迅速に対応しながら開発します)。Node-AIチームでは1週間スプリントを採用しています。大まかな1週間の流れとしては以下の通りです。 水曜PM スプリントプランニング(2−4時間程度): スプリントの始まり、スプリントの詳細な計画をチーム全員で立てます(このスプリントのゴールは何か、どんな機能をどのように作るのか、等を決めます)。 木曜〜火曜 朝会(毎日15分): 毎日決まった時間にチーム全員が集まり、15分間でチームの一日の行動計画を立てます。障害になっていること(課題)の共有も行い、朝会後に別途課題解決の時間を設けることもあります(2次会)。 プロダクトバックログリファインメント(週1回1時間): 直近のスプリントで実施予定のプロダクトバックログ(機能等)に関して、開発者とプロダクトオーナーが協力して仕様の詳細化や開発規模の見積もりをします。 デザインミーティング(週1回1時間): スクラムに定義されていないNode-AIチーム独自のイベントです。デザイナー、プロダクトオーナー、開発者が協力し、新規機能の画面デザイン検討やユーザーヒアリング等の結果を受けて既存機能のUI/UX改善に関する議論をしたり、ペルソナの作成・修正等を行います。 水曜AM スプリントレビュー(1時間): そのスプリントで完成した成果物(Node-AIの場合は動くソフトウェア)を実際の顧客などのステークホルダーにデモのような形で披露しフィードバックを得るイベントです。このフィードバックによってその時点で顧客にとって本当に価値のあるプロダクトになっているかチーム全体で確認ができ、またプロダクトの価値の最適化(プロダクトバックログやリリース計画の見直し等)を図ることができます。 スプリントレトロスペクティブ(1時間〜1.5時間): スプリントの最後に行われ、スプリントの振り返りを行うイベントです。そのスプリントにおけるスクラムチームの動きなど主にプロセスに注目して振り返りを行い、良かった点や改善したほうが良い点・改善案などを出し合います。Node-AIチームではKPT(Keep・Problem・Try)やFDL(Fun・Done・Learn)、Timelineといったフレームワークを使ったり、みんなで雑談しながらチーム状況を振り返ったりと、いろいろと状況によって使い分けたりしています。 【振り返り例(FDL)】 1週間スプリントだとイベントなどのコストが増えやすいですが、その分細かく軌道修正できる・詳細な計画が立てやすい・改善が進みやすいなどのメリットもあります(個人的には特にアジャイル開発に慣れていないチームほど短いスプリントをおすすめします)。またNode-AIチームでは各イベントのファシリテーター(進行役)を固定せず、ローテーションで回しています。これによりファシリテーターの負荷分散やチームメンバーの誰もがチームをリードする意識、当事者意識を持てるように工夫しています。 現在、日々の開発やイベントはすべてリモートワークで行っています。スクラムでの開発ではチーム連携が非常に重要であるため、NeWorkやSlack、Trello等のコミュニケーションツールやタスク管理ツールをフルで活用しながら、リモート環境下でも、なるべく誰がどこでどのような作業をしているのか可視化したり、ペア作業や雑談等を行いやすい環境を整えています。 またここ最近は実施できていませんが、チームの結束を高めたり、チームビルディングを効果的に行うために「開発合宿」を定期的に実施したりしています。こういった活動もアフターコロナにおいては積極的に実施していきたいと思っています。 【開発合宿】 終わりに ここまで長文を読んでいただきありがとうございました。今回の記事から技術部分も開発体制もさらに掘り下げて様々な発信をしていこうと考えていますので、今後ともNode-AIをよろしくお願いします!
アバター