令和時代に『ITの7つの無意味な習慣』を続けないための基礎知識

はじめに

あけましておめでとうございます。

今年一発目の記事ですが下記の記事がなかなか良い問題提起をされていたので、自分なりの「では具体的にどうするの?」に関して少し書いていきたいと思います。 qiita.com

【7位】 2要素認証でない「2段階認証」

記事でも言及されているますが、二段階認証と二要素認証は違います。が、Google様は二要素認証を二段階認証と呼んでるので混乱の元でもあります。 それはそれとして、セキュリティ面ではNISTの定義する二要素認証のが強固なのは事実なので可能な限り速やかに移行するべきです。

そもそも認証をすべて自分で実装するのはセキュリティ面でかなりおすすめできないのでAuth0とかを使いつつAzureADやGoogle Cloud IdentityのようなIDaaSやソーシャルログイン系を使ってそちらに認証を任せることで、2段階認証を駆逐できるでしょう。認証システムはマイクロサービス化して運用するのが最も容易かつ効果の大きい部分なので、マイクロサービスの最初の一歩としても有効です。

業務的にコアではないパーツに関しては内製主義を辞めて、餅は餅屋に任せるのが対策の早道ですね。「顧客のことを思うからこそ外注する精神」です。

ちなみにこの辺は日本以外でもだいぶ混乱というか混沌としているのでNISTがアナウンスして推進してるので殊更USに比べて日本が遅れてるわけではありませんし、そもそもIPAでもNISTの和訳版をちゃんと作っていることは彼らの名誉のために言及しておきます。

【6位】 パスワード後送信付きZIP暗号化ファイルメールの無駄な処理

これは本当にやめたい。ただし、多くの会社ではISMSとかでパスワード付き暗号化zipを送ることがセキュリティ対策として明記されているので担当者レベルではどうこうできません。

でも、安心してください。ISMSやその他類似の個人情報取扱いおよびセキュリティ系の規約には知る限りで「PPAP」を使うことを明記されたものはありません!

なので、以下の観点をベースにリスク管理部とか情シスとかセキュリティとかそういう感じの部署と戦うと良いでしょう。

  1. SSL暗号化メールは多くのメールサービスがサポートしているので必須で対応する(NOT S/MIME)。これにより添付ファイル及び本文の完全性と機密性は担保できる
  2. 誤送信等のリスクを下げるためにはBOXやOneDrive, Google Driveなどのオンラインストレージが有効。社外への送信禁止も容易に設定できるし有効期限や送り先メールでの認証がかけれるので利用しやすく安全。一応、パスワードもつける事が可能。
  3. 個人情報など秘匿性の高い情報を送るときにはパスワード付きZIPでは心許ないのでファイルベース暗号をMS AIPやPGPで実施してオンラインストレージで共有すると良い

ルールが決められた当時はメールはSSL暗号化は普及してなかったでしょうし、多くの中継サーバをメールは経由してたはずなので穴は有りつつも意味はありました。

ポイントはZIP付き暗号よりセキュリティを担保しやすい仕組みが今はあるという事をロジカルに「他者が説明しやすいように」説明することです。

なので相手を否定せず今はより良い仕組みがあるのでインシデントを防ぐためにアップデートが必要な事、技術の進化で監査コストも減ることを根気よく説明するしかないでしょう。自分が担当ならラッキーですね!

特に誤送信対応としてはパスワード付きZIPよりも送った後に権限を消せたり宛先でコントロール出来るオンラインストレージが圧倒的に便利です。メールとも最近は統合されてるので添付ファイルっぽく取り扱えますしね。

この辺はアメリカ等ではすでにそのように文化が変わっているので、その辺のユースケースを前例として出すのも説得の良い材料になるかもしれないです。

また、上記を実現するためにオンプレでやるのは辛すぎるので積極的にOffice 365やG SuiteなどのSaaSを積極的に使って行くべきです。

【5位】 出社してオフィスで使うことが前提のIT基盤

リモートワークをどの程度推奨するかは会社の組織方針であってIT基盤とは違うので触れませんが、適切なセキュリティの仕組みを組むために従来のイントラ型の思想のオフィスITではなく、リモートもオフィス内も区別しないゼロトラスト型のIT基盤を導入することは重要です。

ロケーションとコンテキストを加味したAzureADやCloud IdentityのようなIDaaSをSSO基盤とすることでSaaSやオンプレの区別無くきめ細やかなセキュリティを提供できます。

個人的には入退出チェックや監視カメラなど物理セキュリティの観点でオフィスは優れていると思うので、リモートとオフィスのアクセス権限を業務内容に応じて適切に振り分けるのが必要でしょう。

GmailOutlook.comはやはり便利ですよね!

【4位】 企業ネットワークのインターネットとの接続口を1か所に絞る

これは指摘の意図が良く分からないのですが、多層防御の観点でも接続口をIPなどで絞るのは有効ですし良くやるかと。

SNATなどで外部のアクセス境界を作るのは依然として有効です。そのアクセス元からコンテキストを判断するのは適切なコントロールです。入口あるいは出口のみで対策するのが不足なのは事実なのでFWやEDR、強固なSSOやMFAなど様々な仕組みを組み合わせましょう。

【3位】 従業員の生産性を下げるIT環境

これも本当になんとかしてほしいですよね! シャドウITは情報漏えいの始まりなので最適なITを提供することはセキュリティ対策の第一歩でもあります。

とはいえ前述している通りゼロトラストの考え方を導入したインフラ、モニタリングのためにSSOが重要ですし、利用するアプリとしてはメールやオンラインストレージ、SlackやTeamsなどのチャットが筆頭でしょう。

自社で提供するのも良いですがSaaSを利用することで比較的低コストにセキュリティを担保出来るのでISO27000やSOC2など適切な認定を持っている事を確認した上で利用するのが良いと思います。

また、CASBやDLPなども設定してクラウドを統制し積極的に利用できる状態を作ることも重要です。

なお、BYODに関しては社給端末を用意できない&個人情報など特に重要な情報を取り扱わない場合には利便性も高いと思います。

こちらもIntuneのMAMやオンラインストレージを使ってローカルにファイルが保存されるリスクを減らす事で情報流出のリスクを下げる事ができます。

【2位】 紙原本主義/ハンコ主義

はんこ押印ロボも去年は話題になりましたね。

japanese.engadget.com

これに関しては実は法律的に原紙保管は段階的に緩められてきてるので徐々に改善することが期待できます。経理処理周りも含めて今はかなり緩和されているはずです。古くからある会社はルールが更新されてないケースは多いと思いますが。

ただし、経理/総務/法務/セキュリティなどの部署との連携が重要です。

特にこの手のものはガバナンスを利かせるためにいろんな社内規約に明記してあります。単にシステムの導入だけではなく、その辺の運用を変えるコストも含めて考えていく必要があります。

正直あまり得意な事ではないですが、技術的に意味がない、という当たり前の事実を突きつけるだけでは解決しないのでどうやって法令準拠をするかも合わせて対応する必要があります。

【1位】 パスワードの定期的変更、パスワードでのログイン

個人的にはパスワードZIPあたりを1位にしたいのですがパスワードの定期変更も無意味な習慣にふさわしい内容ですよね。

MSのアナウンスやNISTやIPAもパスワードの定期的更新はあまりセキュリティに寄与しないことが触れられており、新しい常識としては定期更新しなくて良いからMFAとリスクベース認証なのかなと思っています。

これを実現するためにはSSO基盤の導入が実質必須です。

というのも、各アプリケーションで高度な認証を個別に実装するのは困難ですし、ユーザフレンドリーでもありません。なのでSAMLフェデレーションなどを使いIDaaSなどのSaaS基盤を導入することで、一元的なアカウント管理が可能になります。

よくSSOでパスワード流出時のリスクを言われますが、どうせパスワードは事実として使いまわされていますのでSSOにしてリスクが増えるわけではありません。むしろモニタリングコストやリスクベース認証の有無を考えると、個別のアプリでパスワード管理する方が水飲み場攻撃にされされるリスクが高いとさえいえます。

ただし、PCI-DSSはなぜか最新版でも明示的にパスワード変更を要求してくるので困りものです。なのでパスワードを使わないという選択肢も考えられます。

パスワードは「Something They Know」に属するセキュリティ要素ですが使い回しも多く現実的に高いセキュリティを保つのが難しい方式です。そのため、別な要素をベースに認証するパスワードレス型の認証が注目されています。

FIDO2という形で標準化され、Windows Hello for Businessや幾つかのサードパーティ製品で実現することができます。IDaaSなどのSSOと組み合わせる事で個別のアプリに対応しなくても対応させることが出来ますので、PCI−DSSやGDPR対応のついでに全認証システムを刷新するとか、Windows7からWindows10のマイグレのタイミングとか社内のActiveDirectoryのEOSLとかタイミングを合わせてやると良いんじゃないでしょうか。技術的難易度はそこまで無いと思うので。

まとめ

さて、指摘されていた7つの無意味な習慣に対して私なりの見解を書いてみました。

キーワードとしてはゼロトラスト、SaaSの活用、そしてガバナンス対応です。特に単に技術的な議論をするだけではなく後者を話さないと多くの企業では採用は困難でしょう。

幸い、有名なSaaS企業はこの手のガバナンス対応にも積極的です。多くのコンプライアンス系の認証を取得しているのでそちらを足がかりにルールをアップデートしていくのが良いと思います。

ITを頑張るためにはIT以外を頑張る必要があるのは仕方ないですね。でも、ITを知らないとアップデートも出来ないので両方頑張るか担当者と仲良くなって情報交換を行うのが大事です。

それではHappy Hacking!

2019年 IT News & Topics

はじめに

以下の動画で個人的に2019年 IT News & Topicsをまとめましたので、そのフォローアップ記事になります。 自分の好みでまとめてるので今年書いた記事ともいくつか当然被ってるので今年のブログ記事振り返りを兼ねてまとめてみます。

www.youtube.com

Top 10

Quarkus、GraalVM 、OpenJDK

50msというあり得ない速度で「起動」するQuarkusです。HelidonやMicronautなど同様のアプローチのものはあったのですがMicroProfile対応 + Native Image対応という点とコミュニティの勢いで一躍躍り出た感じですね。 こちらの記事でも記載の通りServerlessの登場により今までは強く要求されていなかったスピンアップタイムの極小化に焦点をあてたプロダクトになります。 koduki.hatenablog.com

JavaEEは元々k8s的な機能を内包して独自の世界を作っていましたが、今後はk8sなどのエコシステムと統合する事を前提にスリム化することが期待されます。

同じく今年、Oracle Code Oneでも大きく取り上げられていたのがGraalVMです。 koduki.hatenablog.com

GraalVMはJavaの新しい実装ですがRubyPythonなどのスクリプト言語を動かす環境でもあります。速度が注目されがちですが「RubyでJFRなどJavaのエコシステムの機能を使う」みたいな応用方法もあるので今後の運用が気になるところです。

AOTコンパイルによりJavaをNative Imageに変換できるためQuarkusを筆頭にスピンアップタイムの改善やCLIのコマンドをJavaで作成しやすくなります。

OpenJDKに関しては今年で「Java Still Free」はある程度浸透したのかな、と思います。もちろん、誤解が完全に解けたわけではないのですが元々興味がある人たちのところにはいったん届いたのかな、と言う感じ。 Oracle Code Oneで驚きの新発表が無かったのもオープンな開発がきちんとできてる証拠なので良かったと思います。来年は結構大きな仕様変更も入るので楽しみですね!

Serverless on Kubernetes - Cloud Run, EKS Fargateなど

GoogleのCloud Run, AWSのEKS Fargate, AzureのKEDAなどManagedなk8sの上にCaaSやFaaSを置く試みが登場しています。 qiita.com qiita.com

個人的にしばしば繰り返して主張してるのですが、これはやはりみんなk8sを運用したくない、という事に対する回答だと思っています。

そもそもk8sをなぜ使いたいのでしょうか? それはコンテナを適切にリソーススケジュールしたり、無停止リリースしたり、ロギング/モニタリングしたり、サービスメッシュと統合したいからですよね? k8sはそれを実現できますが本質的に基板よりなのでそもそもGAEやHerokuあるいはAWS Lambdaを運用するための技術なのです。なので開発者/サービス運用者の視点で見ると少し複雑です。 かといって好きなバージョンの言語やライブラリは使いたいしベンダーロックインも避けたいので謹製のPaaSやFaaSはなるべく避けたい。だからk8s使ってるって感じですよね。

現状はk8sの基本的な機能が整ってきたのでこのようなもう一段階抽象度を上げたレイヤーを各社がこぞって作成して、ベンダーロックフリーなサーバレスを確立しようとしていると思います。

現時点ではFaaSやCaaSでゼロインスタンスになる事を前提としたアプリケーションは少なく既存のアプリケーションを動かすのは困難ですが、手前味噌ですが「Kuda」や「gsu」みたいにサーバレスにデプロイするアプリも増えてくるはずです。CMSとかBI Toolとかも相性良さそうですし。

Docker Enterprise事業売却

さて、そんなわけで絶好調に儲かってそうなDocker incですが実はそんなことはありません。 そもそも現在流行ってるのはDockerではなくk8sです。実際にk8s上で動いているコンテナもcri-oとかより軽量なものとなっています。

もちろん、開発環境というかデスクトップではDockerを使用している人が多いでしょうが元々商売のタネはManaged/Hostingでした。 そこでDocker-swarmをベースとしたDocker Enterpriseというサービスを展開していたのですが残念ながらk8sに負けたので先細りの状態のようです。

今後はbakeのようなビルドツールやコンテナを利用した開発環境を進めていくようなのでそれはそれで今後が楽しみです。

これはコンテナ界隈においては一つのターニングポイントかなぁ、と。

オープンソースベンダーの反発とその後

MongoDBの言葉に端をはっするAWSOSSベンダーの争いです。ちょうど最近も追加の燃料が投下されました。

そこでうまい事やったのがGoogleです。Cloud NextですかさずOSSコミュニティと仲良くやっていくという宣言をし、実際にGCPにredisやconfluentのマネージドなサービスを置きユーザはGCPにお金を払ってGCPから各ベンダーに費用が回るというスキームを作りました。 これで、お互いの商売がうまく回りますしユーザとしてもGCPにお金の払い先をまとめれるので便利です。

Docker社もそうですがやはりプロダクトの開発元の会社には何らかのベネフィットが回る仕組みも大事だと思います。

不揮発性メモリが商用DBで採用

MRAMなど不揮発メモリはITクルクル詐欺の代表格の一人です。ちなみに他にはIPv6とか光磁気ディスクのローカルディスクとかがノミネートしてあります。 今回、Intelの3D XPoint Optane DC がついに商用DB --- Oracle Exadataに採用されました。

koduki.hatenablog.com

Exadata自体はおいそれと買えるものじゃないとはいえ、不揮発性メモリが研究室から出てきたのは大きな進歩だと思います。 GCPでもまだ申請しないと使えないはいえM2シリーズとして12TBのOptane DCが利用できるので不揮発性メモリを前提にしたアーキテクチャの研究も進むんじゃないかと期待せざる得ません。

また、データベースとしては下記のような現代的なハードウェアを前提としたアーキテクチャも登場しています。 特にTsurugiに関しては国産OSSと言う意味でも、INSERTつよつよのシリアライズトランザクションDBという意味でも頑張って欲しいです。 koduki.hatenablog.com koduki.hatenablog.com

量子コンピュータ

夢の未来技術といえば、VR核融合、そして量子コンピュータです。このうちVRはすでに手のかかるところまでやってきました。核融合に関しては「Amazonのジェフ・ベゾスCEOが110億円を投資した核融合発電所が2025年に始動 - GIGAZINE」という話もあるので爪の先くらいはかかってるのかもしれません。 そして、量子コンピュータに関しては指の先くらいはかかっておりすでに稼働しています。

といっても速度は実は古典的コンピュータつまりふつうのIntel CPUの方が速いのです。今年はGoogle量子コンピュータの方が速い計算を見つけた、というのが大きな話題を呼んでいたと思います。 ここは10年20年先の分野だと思いますがスパコン同様に離されと追いつけない分野なので日本も頑張り続けてほしいなと思います。(小並感)

ARMとRISC-V

Snapdragonの新作がでたり相変わらずモバイルでは好調のARMですが、今年はサーバやHPCの分野でも活用も見えてきました。

ARM64FXを採用したポスト京の富岳がGreen500の一位をとりましたし、去年に引き続きAWSで独自設計の次世代ARMプロセッサ「Graviton 2」ARMチップをリリースしました。

OpteronのAシリーズの時と違って今回は内需というかある程度ユースケースが見えての登場なので継続性が期待でき、ITクルクル詐欺の汚名を返上できそうです。 HPC分野にしてもサーバ分野にしても実際のところ省電力/電力効率が非常に重要なので今後x86の牙城を一定レベルで崩すことが期待されます。

また、RISC-Vも色々ありました。特に、アメリカの経済制裁の影響もあってHuaweiやAlibabaといった中国からのコミットが多かった印象です。 研究および実用でOS対応や2.5GHz/16コアといった高性能チップを開発するなど、政治抜きに考えれば良いことだなと思います。本質的にOSSは自由であるべきですし。

もちろん中国だけで利用されているとこではなく、WDもきっちりチップを約束通り作ってきましたし、GoogleのOpenTitanにもRISC-Vが使われています。 日本の事例としても理研創薬専用スパコンのコントロールチップとして利用しています。Googleなんかは内部的にはもっと使ってる可能性も高いですしRISC-Vも徐々に流れが出来てきた感じです。

MFAとZero Trust Network

MFAに関しては今年は日本では結構騒がれましたね。7-Payの件で。

koduki.hatenablog.com

Zero Trust NetworkはMFAなども活用して既存のVPNよりもセキュアなアクセスを行う方法の総称です。次世代VPNと呼んでいいでしょう。 これに関しては元々GoogleのByondCorpなど概念はありましたがこの1,2年でセキュリティベンダー含めてワーディングをしてきて徐々に浸透してきたかと思います。

これに関してはまた来年ブログか動画を作りたいと思います。

5Gとクラウド

5Gの目玉は速度以上にレイテンシーです。しかしながらその超低レイテンシーを活かすにはエッジコンピューティングが重要です。 エッジコンピューティングは「どこをエッジと定義するか」というのが揺れているというか定まってないので時にはデバイスだったり時にはCDNだったりするのですが、今回はモバイルネットワークです。 具体的には基地局の中か近距離に小さなDCを置く事になります。ここにサービスをデプロイしてAWSGCPなどのインターネット上のクラウドよりも素早い動作をさせることになります。

この分野、各キャリアがどうやってクラウドベンダーになるか? って部分が個人的にとても気になってなのですがAWSという巨人がKDDIと組む形で日本に乗り込んでくるようです。 こうなると当然他のキャリアやメガベンダーも乗って来るわけでGoogleやAlibabaの動きがきになります。

Oculus Questのリリース

最後にOculus Questのリリースです。 Oculus Questは携帯可能であのスペックという点で既存と一線を画する存在です。

とか言いながら私はVive Cosmosを買ったので全員がOculus Questを買うわけではないだろうと思いますが、普及と言う観点ではどこでもすぐ使えるというのは大きな強みです。 自分自身の使いやすさも当然ありますが、他人と体験を共有しやすくなるので輪が広がるというのもポイントです。

来年はARグラスのn-realが出ますのでここが一つターニングポイントになるかな? と期待しています。

koduki.hatenablog.com

まとめ

個人的には今年一番の変化は自分がVtuberというかVRやってみたことですね。

特に日本ではアニメ調に極振りされてる事もあって人を選ぶ状態だけど、アバター系はまさに仮想現実って感じをすでに味わえる。 リングメニューが使えるのは本当に衝撃でした。。。 あと本当にアバターが自分の一部のように感じれますしね。

さておき、個人的な興味でクラウドやHPC、あとVRが中心になりましたが如何だったでしょうか? 他の人の書いたTop10もぜひ見てみたいですね。

来年も皆さんおよびコンピュータ様にとって良い年でありますように!

それではHappy Hacking!

Elixirで負荷テストツールを作ってみた

はじめに

社内ISUCON的な物をしてみたいと思いたちここ最近準備をしています。

仕様策定やアプリのリファレンス実装は良いとして考えたのは負荷テストツールです。

選択肢としては3つありました

  1. JUnitやGatlingなどの負荷テストツールを使う
  2. ISCONの負荷テストツールを流用する
  3. 自作

知識の有効活用という意味では1, 先人に学ぶという意味では2を選択するべきでしたが、まあ半ば趣味優先で面白そうだったので自作してみる事にしました。負荷テストツールを真面目に書いた事なかったですし。

もちろん、自分で1から作れば自由度が高いのでやりたい事をもっともシンプルに実現できるだろうという算段も一応あります。

そこで悩んだのが言語です。手慣れたJavaRubyで書くのが一番楽そうですが、負荷テストツールという特性上たくさんの並行処理を管理する必要がありました。それならそう言うのが得意なElixirで行こうと決めました。Goも考えたんですが一応以前触った事はあったのでせっかくなのでかねてより興味のあったElixirで。

とりあえず今回のコードはこちら。

GitHub - koduki/load-generator

負荷テストツールに求める事

今回負荷テストツールには以下の点を求めました

  1. シナリオベースのテスト。値の検証ができる事
  2. 1万程度のユーザ数を取り扱える事(# 同時アクセス数では無くユーザ数)
  3. グラフィカルなレポーティング機能

1と3に関しては概ね完成したものの2に関しては上手く出来てないです。元々、軽量プロセスなら大量プロセスを同時に扱っても死なないかな、と思ってElixirにしたのですが

基本構造

LoadGenerator.Appが本体です。こちらにテストシナリオを書いていきます。

parallel(users_num, fn -> continue(duration, run_user(base_url, test_id, merchant_ids)) end)

parallel関数でユーザ数分プロセスを立ち上げcontinue関数で指定の時間まで再帰で処理を繰り返します。

テストシナリオ側は以下のように普通にプログラムでテスト内容を記述していきます。

    # pay
    amount1 = :rand.uniform(100_000)
    _tid1 = req_authorize(base_url, test_id, account_no, rand_select(merchant_ids), amount1, datetime)

    amount2 = :rand.uniform(10_000)
    _tid2 = req_authorize(base_url, test_id, account_no, rand_select(merchant_ids), amount2, datetime)

    # show summary
    usedMonth = Timex.format!(datetime, "%Y%m", :strftime)
    body = req_summary(base_url, test_id, account_no, usedMonth)

    count = count + 2
    sum = sum + amount1 + amount2

    # validate
    assert = assert_template(body)
    it("validate transaction summary", [
      assert.("accountNo", :should_be, account_no),
      assert.("count", :should_be, count),
      assert.("summary", :should_be, sum)
    ])

assertは検証結果が異なる時に例外を発生するメソッドです。最初はテスト失敗数のカウントとか考えてたのですが500エラーならともかく今回のように計算結果のエラーになるPGは負荷テストを続ける意味がないので例外でアプリを殺す方向に切り替えました。

今の所、同値を判定する命令しか追加していませんが、バリエーションはいくつか増やしていく予定

3のグラフィカルなレポーティングに関してはGCPに投げてBigQuery + Metabaseで解析しています。

qiita.com qiita.com

ローカルで観れた方が便利な時も多いのでローカルに出力するコードとログのビューアも一応入れています。

まとめ

初めてのElixirアプリですがElixir自体は個人的には結構手に馴染んだ感じがしました。

まだβどころかαレベルの品質なので精度をあげてアップデートしていきたいと思います。

2020年の動向が気になるスマートグラス Top 5 + α

個人的にARグラス/スマートグラスに注目しています。

スマートグラスは簡単に言えばGoogle Glassなのですが、最近のものはFocalsを筆頭にかなり普通の眼鏡っぽいデザインになっています。

性能的にはHoloLensのようなヘッドセット型のものより数段劣るはずですが一般市場に受け入れられやすいのはこちらでしょう。Appleがそのうち出すってことでも有名ですね。

という訳で動画の方でも話しましたが、個人的に2020年に動向が気になるAR/スマートグラスをリストアップしてみました。

www.youtube.com

なおスマートグラスは単にメガネに情報を写せる多機能メガネの総称、ARグラスはAR機能(空間認識をして仮想オブジェクを重ねたりインタラクションがある)もとして今回は扱っています。

比較一覧

忙しい人用の比較一覧

<-> Focalsf:id:pascal256:20191215165406p:plain Nreal Lightf:id:pascal256:20191216101859p:plain Vuzix Bladef:id:pascal256:20191216101920p:plain Norm Glassf:id:pascal256:20191216101937p:plain RETISSA Display IIf:id:pascal256:20191216102028p:plain Huawei VR Glassesf:id:pascal256:20191216102105p:plain
XR スマートグラス ARグラス スマートグラス スマートグラス スマートグラス VRグラス
タイプ スマホ接続 専用計算ユニット スマホ接続 スマホ接続 HDMI接続 スマホ接続
プロセッサ Qualcomm APQ8009w
Qualcomm Adreno 304 GPU
Snapdragon 845 ARM Cortex-A53 Dual-Core up to 1.2 GHz ? ?
重量 72.57g 88g 3.6g 36g 40g 166
入力 指輪型コントローラ, Alexa コントローラ タッチパッド, ヘッドモーション, Alexa タッチパッド, ヘッドモーション, ボイスコマンド ?
ヘッドトラッキング なし 6DoF なし なし なし 6DoF
ハンドトラッキング なし 3DoF なし なし なし 3DoF
カメラ なし ? 8 MP, 720p video 8 MP, 1080p video なし ?
入手/一般販売 2020年 リブート 2020年 Amazon 2020年。KickStarter 2020年 2020年

North Focals 2.0

ほとんどメガネという衝撃的なデザインのFocalsの2.0です。 blog.bynorth.com

これまでスマートグラスやARグラスはメカメカしいデザインが多かったのですが、普通のメガネのデザインが可能だと示したのが印象的でした。2020年には2.0が出るという事で否応にも期待が高まります。

インテルから買ったVauntの特許もタイミング的に2.0から本格的に入ってくると思うので楽しみです。たしか網膜投影技術とかもあったはず。

www.moguravr.com

元々ニューヨークとトロントの店頭でしか購入できなかったのですが現在はiPhone X以降のTrueDeps搭載端末であればアプリから購入が可能です。

カメラもなく空間認識能力もないのでAR/MR的な事はできずあくまで情報を表示するためのスマートグラスとなります。このあたりは彼らのコンセプトでもあるのでおそらく2.0でも変わらないでしょう。

以下のレビューが詳しいです。 techcrunch.com

Nreal Light

デザイン性ではForcalsに一歩譲るものの性能で圧倒するのがNrealです。

www.nreal.ai

こちらはサングラス風のデザインでありながら6DoF トラッキングによる空間認識を実現するARグラスです。

www.youtube.com

しかも$499という格安。PCやスマホと接続する必要はありますが、この値段と性能そしてデザインや軽さは驚異的です。 一般向けには2020年に発売と言われています。KDDIとの提携が発表されているので日本でも入手しやすいARグラスになる見込みです。

news.kddi.com

www.moguravr.com

正直、このサイズや形状で空間認識をやるのはもう何年か先になると思ってたので、さすが中国と言わざる得ません。SDKのデモを見る限りではそれなりに動いてるように見えるので入手できるようになり次第購入したい一品です。

www.youtube.com

Vuzix Blade

スマートグラス業界の老舗の一つがVuzixです。

www.vuzix.com

以前は少し野暮ったいデザインのデバイスだったのですが、今年出したBladeはかなりすっきりしたデザインです。

カメラは付いていますがNRealのような空間認識をするものではなくForcalsのようにスマホの情報をメガネに表示するタイプの端末です。Alexaなどとも連携できます。本家オンラインストアおよびAmazonで買えるので現在最も購入しやすいARデバイスだと思います。

おそらくGoogle Glassの初期コンセプトに最も近いものがこれです。以下のレビューでも写真をよく取ってます。

www.youtube.com

また、日本だとメルカリがアプリを作っています。

www.youtube.com

翻訳アプリもGoogle LensやGoogle Translate的な機能がメガネ出来るとなると格段と使いやすくないそうですね。

vrinside.jp

すでに発売しているので多くのアプリが出始めているのがVuzix Bladeの魅力の一つです。

とはいえHWの進化も競争してるタイミングなので、多くの競合に刺激されて来年新しいバージョンが出ないかが気になるところですね。

Norm Glass

KickStarterクラウドファンディングで作られているのがNorm Glassです。

www.kickstarter.com

基本的なコンセプトは Vuzix Bladeと同じだと思いますがスペックは少し上な気がします。まだあまり詳細は無いのですが予約購入してるので届いたらレビュー書きたいと思います。

RETISSA(R) DisplayⅡ

今回唯一の国産製品。おそらく世界初のレーザ網膜走査型の網膜投影ディスプレイを利用した商用ARグラス。

pc.watch.impress.co.jp

ディスプレイに表示するのではなく目に投射するので原理的には近視とかの影響を受けない。

仕組みはこの記事が分かりやすかったです。

xr-hub.com

エンタープライズ製品だとは思うがアプリケーション部分の情報が少なすぎるのが懸念。Holo Lensの対抗馬として広めるにはその辺も頑張る必要がある。敵強すぎる問題。。。

あるいは基礎技術のリファレンスとしての位置付かも? 最終的にはレーザ網膜走査型の網膜投影ディスプレイをHolo Lens達が採用すれば勝ち的な。

Huawei VR Glasses

グラスというかヘッドセット型ですが十分にコンパクトなので紹介します。そもそもVRだけど。 https://www.vopmart.com/huawei-vr-glass.htmlwww.vopmart.com

166gという重量、厚みが26.6mmとVive CosmosはもちろんOculus Questと比べてみても写真を見る限り相当小さい。もうヘッドセット言う感じじゃないですね。

スマホを子機にすることでプロセッシング周りは外出してると思われる。GearVRみたいなスマホの画面をディスプレイに使うものよりこっちのが使い勝手が良さそうですね。

ゲームとか映画見るのがメインターゲット。IMAXともパートナーを組んでるみたい。

6DoFもサポートとのこと。詳細は下記の動画。

www.youtube.com

まとめ

個人的にAfter Focalsと呼んでる製品群を並べてみました。実際はFocalsが牽引したというより各社独自に辿りつたんだとは思いますが、やはり2019年以降のデザインはスタイリッシュなことが前提です。

このあたりはVR以上に日常的に身につける要素が強いスマートグラスならではですね。

ARグラスはスマホを超える身近なデバイスとして一気に普及する可能性がある製品だと思います。視界の拡張とハンドトラックはUXの幅を変えます。

正直今は今のスマホに対しての当時のPDA的な成熟度だとは思いますが、今後の動向をしっかり追って行きたいところです。

しかし、KDDIAWSといいNRealといい5Gに向けてのパートナーが良いところ揃えてきてるなぁ。

参考

JFRをBigQuery/Metabaseのオレオレダッシュボードで可視化する

この記事は Java Advent Calendar 2019の9日目です。前日はSphereさんの「Visual Studio CodeでParaya MicroのWebアプリケーションを作る準備をする: すふぃあの記憶」でした。

はじめに

JDK Flight Recorderを皆さん使っていますか? JDK 9からAPIが整備されJDK 11からOpenJDKに寄贈されたので商用ライセンスを持っていなくても本番で自由に使えるようになりました。

常時プロファイルをonにして障害時にすぐに情報を取得できるブラックボックス分析はとても便利です。OpenJDK系ディス鳥の標準機能なので、まだ利用されてない方はぜひ使って見てください。

JFRはあまり詳しくないという方は、更新があまりできてないですが個人的にJFRの情報をまとめてるサイトも書き始めたので良ければご参考ください。

koduki.github.io

閑話休題。JFRはJDK Mission ControlないしはVisual VMで見るのが基本なのですが、対象のJFRファイル以外の情報と統合したくてデータを抽出したいことも良くあります。

事後の分析を複数環境に対してやりたいときやPVとの相関を見たいとき。そんな時は仕方ないのでExcelにコピペして集計したりしました。

これを解決しようとすると、以前は非公式APIを使うとかあまり便利な方法が無かったのですが、JDK9以降はAPIが標準化されて便利になったのでそのやり方を紹介したいと思います。なお、基本的にはCode Oneで喋ったのと同じ内容となります。

JFRを取得する

JFRを取得しないと始まらないのでJFRの取得をします。

WebアプリケーションだろうがバッチだろうがJFRの取得はJVMオプションを指定するだけで簡単に出来ます。

今回はQuarkusで作ったRESTベースのWebアプリです。

java -XX:StartFlightRecording=settings=profile,disk=true,filename=/var/log/myapp/myapp.jfr \
-XX:FlightRecorderOptions=repository=/var/log/myapp/reposistory / \
-jar myapp-runner.jar

-XXオプションを-jarの後に書いた場合は正常に動かないので注意してください。

ポイントはrepositoryオプションを使ってリポジトリを指定することです。filenameで指定するJFRファイルはプロセスの終了時かJFR.dumpコマンドの実行時にしか出力されません。

それだと、不便なことも多いので循環バッファの一部をディスクに出力するようにrepositoryオプションを指定すると特にWebアプリケーションでは便利です。

以前は必要だったUnlockCommercialFeaturesFlightRecorderオプションは不要です。また、JDK 8の頃とはJFRに必要なJVMオプションがJDK8から少し異なっています。例えば、以下のような差分があります。

JDK 8:

-XX:+UnlockCommercialFeatures
-XX:+FlightRecorder
-XX:FlightRecorderOptions=defaultrecording=true,
dumponexit=true, dumponexitpath=/var/log/myapp/myapp.jfr

JDK 11:

-XX:StartFlightRecording=dumponexit=true,
filename=/var/log/myapp/myapp.jfr

JDK9のオプションがJava Flight Recorder Command Referenceで確認できるので見直しておくのが良いと思います。

起動したWebアプリケーションに負荷を掛けてみます。すると以下のようなディレクトリ構成でJFRファイルが出力されます。

$ tree /var/log/myapp/ 
/var/log/myapp/
├── myapp.jfr
└── reposistory
    └── 2019_12_01_23_43_51_89649
        ├── 2019_12_01_23_43_51.jfr
        ├── 2019_12_01_23_53_42.jfr
        └── 2019_12_01_23_53_42.part

myapp.jfrは基本的にはプロセスが終了するまで空なので、スローレスポンスの分析のために中間の状態を取得したい場合はrepository配下のファイルを取得することになります。障害時にはプロセスが終了するのでmyapp.jfrを取得することになるでしょう。

今回はプロセスを落とさずにJFRを取得したかったのでリポジトリから2019_12_01_23_43_51.jfrを取得しています。

とりあえずJMCで開いてみるとこんな感じです。

f:id:pascal256:20191202182654p:plain

JFRをJSONに展開する

続いて、JFRファイルを取り扱いやすい形式に変換しましょう。

JFRファイルはバイナリです。これはこれで便利なのですが、他のツールと連携する場合には不便です。なのでテキストベースの形式に変換します。

JFRはJDK JFR Consume APIを使うことでJavaを使ってJFRを解析出来ます。自分で解析PGを組んでも良いのですが、JFR Toolを使うことでXMLJSONに変換できるので今回はこちらを使います。

以下のコマンドでJFRファイルをJSONテキストに変換できます。

$ jfr print \
--json  \
--events jdk.GCPhaseParallel,jdk.ExecutionSample,jdk.CPULoad,jdk.GCHeapSummary \
2019_12_01_23_43_51.jfr > 2019_12_01_23_43_51.json

categorieseventsはJFRのイベントブラウザから確認できます。こちらの情報を使って内容をフィルタする事ができます。

JSONの中身は以下のようにJFRの値を単純に変換した形となります。

{
  "recording": {
    "events": [{
      "type": "jdk.GCConfiguration", 
      "values": {
        "startTime": "2019-12-01T23:43:51.141988376-08:00", 
        "youngCollector": "G1New", 
        "oldCollector": "G1Old", 
        "parallelGCThreads": 4, 
        "concurrentGCThreads": 1, 
        "usesDynamicGCThreads": true, 
        "isExplicitGCConcurrent": false, 
        "isExplicitGCDisabled": false, 
        "pauseTarget": "PT-2562047788015215H-30M-8S", 
        "gcTimeRatio": 12
      }
    }, {
      "type": "jdk.GCSurvivorConfiguration", 
      "values": {
        "startTime": "2019-12-01T23:43:51.141992948-08:00", 

容量を確認してみましょう。

$ ls -lh 2019_12_01_23_43_51.jfr 2019_12_01_23_43_51.json
-rw-r--r--  1 koduki  staff    18M Dec  1 23:56 2019_12_01_23_43_51.jfr
-rw-r--r--  1 koduki  wheel   25M Dec  2 20:36 2019_12_01_23_43_51.json

元が18MBだった展開すると25MBになっているのが分かります。あくまでフィルタをした状態のサイズで仮にフィルタをしない場合は3.2GBくらいのサイズになり、JFRの保存形式の効率の良さが伺えます。

また、events配列の中身だけあれば良いのとJSONはNDJSON(Newline delimited JSON)形式にする必要があるのでjqコマンドで変換します。

$ cat 2019_12_01_23_43_51.json | jq -c '.recording.events[]' > 2019_12_01_23_43_51_f.json

これでひとまずのJSONへの展開は完了です。

BigQueryに取り込む

では、BigQueryに先ほどのJSONを取り込みましょう。

まず先ほどのJSONをイベント毎のファイルに分割します。

$ echo "jdk.GCPhaseParallel jdk.ExecutionSample jdk.CPULoad jdk.GCHeapSummary" \
|xargs -P4 -n1 sh -c 'grep \"type\":\"$0\" 2019_12_01_23_43_51_f.json \
> logs/${0}.json'

イベント毎フォーマットが異なるので単純にBigQueryにアップロードしようとするとエラーになります。

INSERT文を使うか、別の形式に変換して可変部分をJSON文字列にしてSQLで解析するというELTっぽい手もあるのですが、それはそれで面倒なので今回はシンプルにイベント毎にテーブルを分けてロードすることにします。

続いてGCSにアップロードします。

# Create Bucket
$ gsutil mb gs://jfr-storage/  

# Upload
$ gsutil cp logs/*.json gs://jfr-storage/

# Check
$ gsutil ls -lh gs://jfr-storage/ 
 88.88 KiB  2019-12-03T08:42:43Z  gs://jfr-storage/jdk.CPULoad.json
  7.31 MiB  2019-12-03T08:42:56Z  gs://jfr-storage/jdk.ExecutionSample.json
 104.4 KiB  2019-12-03T08:42:56Z  gs://jfr-storage/jdk.GCHeapSummary.json
  5.37 MiB  2019-12-03T08:43:05Z  gs://jfr-storage/jdk.GCPhaseParallel.json
TOTAL: 4 objects, 13492203 bytes (12.87 MiB)

BigQueryにロードしていきます。

bq load --autodetect --source_format=NEWLINE_DELIMITED_JSON jfr_logs.jdk_CPULoad gs://jfr-storage/jdk.CPULoad.json
bq load --autodetect --source_format=NEWLINE_DELIMITED_JSON jfr_logs.jdk_ExecutionSample gs://jfr-storage/jdk.ExecutionSample.json
bq load --autodetect --source_format=NEWLINE_DELIMITED_JSON jfr_logs.jdk_GCHeapSummary gs://jfr-storage/jdk.GCHeapSummary.json
bq load --autodetect --source_format=NEWLINE_DELIMITED_JSON jfr_logs.jdk_GCPhaseParallel gs://jfr-storage/jdk.GCPhaseParallel.json

テーブルが作成されたのが確認できました。

$ bq ls jfr_logs
        tableId           Type     Labels   Time Partitioning   Clustered Fields  
 --------------------- ---------- -------- ------------------- ------------------ 
  jdk_CPULoad           TABLE                                                     
  jdk_ExecutionSample   TABLE                                                     
  jdk_GCHeapSummary     TABLE                                                     
  jdk_GCPhaseParallel   TABLE

Metabaseで可視化する

さて、ではBigQueryに取り込んだJFRのデータをビジュライズしていきましょう。 今回はMetabaseを使って可視化を行いました。GCPにMetabaseをインストールしてBigQueryと繋ぐ方法は下記の記事に別途まとめてるのでご参考ください。

qiita.com

JFRの解析して、とりあえずこんな感じの簡単なレポートを作ってみました。 f:id:pascal256:20191205171856p:plain

SQLは以下の感じで組んでいます。

今回は面倒だったので複数のJFRがテーブルに入ることは無い想定でSQLを組み立てています。実際はその辺を考慮してWHERE句等を書く必要があります。

CPU Load Summary

SELECT 
  values.startTime,  
  values.machineTotal, 
  values.jvmUser 
FROM `{project_id}.jfr_logs.jdk_CPULoad`

GC Summary

SELECT 
  values.name,
  SUM(CAST(REGEXP_REPLACE(values.duration, r"[PTS]", "") AS NUMERIC)) as total_duration, 
  count(1) as count
FROM `{project_id}.jfr_logs.jdk_GCPhaseParallel`
GROUP BY name

Heap Summary

SELECT 
  values.startTime as timestamp, 
  values.heapUsed 
FROM `{project_id}.jfr_logs.jdk_GCHeapSummary`

Methods Call

SELECT method_name, count(1) as count FROM
(
  SELECT 
    CONCAT(
      REGEXP_REPLACE(REGEXP_REPLACE(values.stackTrace.frames[OFFSET(0)].method.descriptor, r";.*|\(L|[\(\)]", ""), "/", "."), 
      ".", 
      values.stackTrace.frames[OFFSET(0)].method.name
    ) as method_name
  FROM `{project_id}..jfr_logs.jdk_ExecutionSample`
  UNION ALL
  SELECT 
    CONCAT(
      REGEXP_REPLACE(REGEXP_REPLACE(values.stackTrace.frames[OFFSET(1)].method.descriptor, r";.*|\(L|[\(\)]", ""), "/", "."), 
      ".", 
      values.stackTrace.frames[OFFSET(1)].method.name
    ) as method_name
  FROM `{project_id}.jfr_logs.jdk_ExecutionSample`
  UNION ALL
  SELECT 
    CONCAT(
      REGEXP_REPLACE(REGEXP_REPLACE(values.stackTrace.frames[OFFSET(2)].method.descriptor, r";.*|\(L|[\(\)]", ""), "/", "."), 
      ".", 
      values.stackTrace.frames[OFFSET(2)].method.name
    ) as method_name
  FROM `{project_id}.jfr_logs.jdk_ExecutionSample`
  UNION ALL
  SELECT 
    CONCAT(
      REGEXP_REPLACE(REGEXP_REPLACE(values.stackTrace.frames[OFFSET(3)].method.descriptor, r";.*|\(L|[\(\)]", ""), "/", "."), 
      ".", 
      values.stackTrace.frames[OFFSET(3)].method.name
    ) as method_name
  FROM `{project_id}.jfr_logs.jdk_ExecutionSample`
)
GROUP BY method_name
ORDER BY count desc
LIMIT 10

簡単なSQLのわりにはそれっぽいレポートが出来てるのは無いでしょうか?

以前はJFRをES + Kibanaでビジュアライズしましたが、BigQuery + Metabaseの方が圧倒的に楽です。

カスタムイベントを追加する

WeblogicならWLDFの情報がJFRに入っていて分析も捗るのですが、それ以外のシステムで値を採ろうと思ったらカスタムイベントを書く必要があります。

以前は、非公式APIを使う必要がありましたがJDK9からはカスタムイベントの仕様も刷新され正式に利用できるようになりました。

ちなみにライブラリがJDK8でも利用される場合は「空実装の互換API」があるようなのでこちらを使えばコンパイルエラーにならないはずです。(#未検証)

とりあえずはWebアプリケーションの基本という事でResponse Timeを取ってみます。

まずはJFRイベントを作成します。

@Category({"Application Profile"})
@Label("HTTP Request")
public class HttpRequestEvent extends Event {

    @Label("Method")
    String method;

    @Label("URL")
    String url;
}

基本的にはEventクラスを継承して、格納したいプロパティをフィールドとして記述するだけです。@Label@CategoryでJMC上からの見え方を制御できます。

アノテーションにはその他にも閾値を設定する@Thresholdなどがあります。スロークエリなど特定の条件のみとれば良いときは設定すると負荷やデータサイズも下がって良いと思います。

代表的なアノテーションは以下です。

アノテーション 概要 デフォルト値
@Category 複数のイベントを束ねるカテゴリの作成。複数指定する事で階層化もできる N/A
@Name イベントの名前。 イベントクラスのパッケージ名を含むフルクラス名
@Label プロパティ名 N/A
@Description イベントの簡易な説明 N/A
@Threshold JFRに記録するための閾値 0 ns
@StackTrace イベントにスタックトレースを含むかのフラグ。デフォルトでは含まれる true
@Enable イベント記録を有効にするかのフラグ。デフォルトでは有効 true

では、続いてJFRイベントを記録します。

HTTPのレスポンスタイムなのでJAX-RSのメソッドをフックすることになります。今回の対象システムはQuarkusを使ってるのでCDIのインターセプタを使ってAOPを行います。

まずはトリガーとなるアノテーションを作成します。

@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface WebTrace {
}

続いて、アノテーションをエンドポイントになるJAX-RSのコードに付与します。

@Path("/account")
@WebTrace
public class AccountResource {
.
.
.

最後にCDIのインターセプタを作成します。JFRイベントの記録もここです。

@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
@WebTrace
public class WebTraceIntersepter {
    @Inject
    HttpServletRequest req;

    @AroundInvoke
    public Object invoke(InvocationContext ic) throws Exception {
        var event = new HttpRequestEvent(); // JFRイベントの初期化

        event.begin();  // JFRイベントの記録開始
        var result = ic.proceed();
        event.end();    // JFRイベントの記録完了

        // JFRイベントに任意のパラメータを追加
        event.url = req.getRequestURI();
        event.method = req.getMethod();
        // JFRイベントをコミット
        event.commit();

        return result;
    }
}

JFRイベントは単にnewするだけで良いです。DIとかFactorytとかが必要なのいで簡単ですね。今回の場合はic.proceed()が実際の処理が実行されるタイミングです。その前後でbegin/endする事で実行時間の記録を行います。単位はナノ秒です。

必要なパラメータを詰めたらcommitします。今回はURLとGETなのかPOSTなのかと言う情報も欲しかったので、HttpServletRequestをInjectionして取得しています。JFR関係ないですが1点注意なのはquarkus-undertowを依存に含めないとQuarkusではHttpServletRequestをInjectionできないので注意してください。

JMCのイベントブラウザで以下のように格納されたのが確認できるかと思います。

f:id:pascal256:20191208033755p:plain

格納したURLMethodはもちろんJFR側の機能としてDurationスタックトレース、あるいは開始と終了のタイムスタンプも取れているので調査が捗りそうですよね?

JFRのカスタムイベントに関しては徳益さんの「使ってみよう!JDK Flight Recorder」か「Java Flight Recorder in JDK 9 – Marcus Hirt」が詳しいです。

カスタムイベントをMetabaseでビジュアライズ

JMCの標準機能だけではグラフにして表示とかはできませんが、JMC Pluginを作れば可能なはずです。ただし、Eclipse力が必要になります。

と言うわけでBigQuery及びMetabaseを使ってビジュアライズしましょう。ちなみに、話の流れ的にMetabaseを使ってますが手元で探索的に作業するだけならgnuplot使うとかJupyter Notebookを使うのも良いと思います。

先ほどと同じ手順でJSONに変換してBigQueryに取り込みます。

$ jfr print --json --events minica.util.profile.HttpRequestEvent myapp.jfr > myapp.json
$ cat myapp.json | jq -c '.recording.events[]' >  myapp_f.json
$ grep '"type":"minica.util.profile.HttpRequestEvent"' myapp_f.json > HttpRequestEvent.json
$ gsutil cp HttpRequestEvent.json gs://jfr-storage/
$ bq load --autodetect \
                 --source_format=NEWLINE_DELIMITED_JSON \
                 jfr_logs.HttpRequestEvent \
                 gs://jfr-storage/HttpRequestEvent.json

Metabaseでグラフにしてみます。

f:id:pascal256:20191208060250p:plain

利用したSQLはこちら。

レスポンスサマリ:

SELECT 
  values.startTime as timpestamp,
  CAST(REGEXP_REPLACE(values.duration, r"[PTS]", "") AS NUMERIC) * 1000 duration
FROM `{project_id}.jfr_logs.HttpRequestEvent` 

URL分布:

SELECT 
  REGEXP_REPLACE(values.url, r"[0-9]+", "{key}") as url,
  count(1) as count
FROM `{project_id}.jfr_logs.HttpRequestEvent`
GROUP BY url

TPS:

SELECT PERCENTILE_CONT(count, 0.5) OVER() as tps FROM(
  SELECT sec, count(1) as count  FROM(
    SELECT 
      UNIX_SECONDS(values.startTime) as sec,
      CAST(REGEXP_REPLACE(values.duration, r"[PTS]", "") AS NUMERIC) * 1000 duration
    FROM `{project_id}.jfr_logs.HttpRequestEvent` 
  ) GROUP BY sec
)
LIMIT 1

JMCでカスタムイベントをビジュアライズしようとするとプラグインを書く必要がありGUI力が足らない私としては結構難しかったのですが、Metabase + BigQueryであれば手慣れたSQLでサクッとグラフに出来るのが良いですね。

リアルタイム連携をする

せっかくなのでリアルタイム連携も作っていきましょう。

と言っても、JFR Stream APIはまだ未実装なので下記のような流れでやります。

  1. JFRのオプションを変更してリポジトリに小刻みにファイルを吐く
  2. JFRをGCPにアップロードする
  3. GCSにアップロードされたことCloud Functionsで検知してCloud Runをキックする
  4. Cloud RunでCloud SDKコマンドを使ってGCSからBigQueryに連携する

Cloud RunはCloud SDKを使うBashとすることで概ね今までの通りの方法で動くのがポイントです。

JFRのチャンクサイズの変更

まずは、JFRのチャンクサイズを変更します。デフォルトでは12MBとかなり大きいのでアップロード負荷が高いです。なので、1MBまで下げておきます。

java -XX:StartFlightRecording=settings=profile,disk=true,filename=/var/log/myapp/myapp.jfr -XX:FlightRecorderOptions=repository=/var/log/myapp/jfr_logs/,maxchunksize=1M -jar myapp.jar

maxchunksizeを変更する事で比較的小さなファイルがリポジトリにたくさん出来るようになります。

JFRをGCPにアップロードする

続いてJFRのアップロードです。 前回実行時とのファイルリストの差分をチェックして新規があればgs://jfr-storage/にJFRをアップロードします。

REPO=/var/log/myapp/jfr_logs
WORKDIR=/tmp

CURRENT_REPO="$REPO/"$(ls -1 $REPO|sort|head -1)
ls -l $CURRENT_REPO|awk '/jfr/{print $9, $5}'|sort > $WORKDIR/jfr.list
diff ${WORKDIR}/jfr.list ${WORKDIR}/jfr.list.prev |grep jfr|awk '$NF > 0{print $2}' > ${WORKDIR}/jfr.diff
mv $WORKDIR/jfr.list $WORKDIR/jfr.list.prev

cat ${WORKDIR}/jfr.diff|xargs -n1 -P1 -I{} gsutil cp "${CURRENT_REPO}/"{} gs://jfr-storage/

watchコマンドやcronで定期実行すると良いでしょう。

GCSにアップロードされたことCloud Functionsで検知してCloud Runをキックする

Cloud RunはまだHTTP以外のイベントソースを受け取れないのでイベントのフックにはCloud Functionsを使います。スクリプトは下記の通り。

package.json:

{
  "name": "check_gcs4jfr",
  "version": "0.0.1",
  "dependencies": {
    "@google-cloud/storage": "^1.6.0",
    "request": "^2.88.0"
  }
}

index.js:

var request = require("request");

exports.checkGCS4JFR = (event, context) => {
  const gcsEvent = event;
  const url = "https://{Cloud RunのURL}?args=" + gcsEvent.name;

  console.log(`Recive Event: ${gcsEvent.name}`);
  request.get(url, function(err, res, body) {
    if (err) {
      console.log("Error: " + err.message);
      return;
    }
    console.log(body);
  });
};

パススルーしてるだけなのでシンプルですね。以下のコマンドでデプロイします。

$  gcloud functions deploy checkGCS4JFR --runtime nodejs8 --trigger-resource jfr-storage --trigger-event google.storage.object.finalize

Cloud RunでCloud SDKコマンドを使ってGCSからBigQueryに連携する

最後にGCSからBigQueryへのアップロードです。と言っても基本的には先ほど記載したコマンドをそのまま実行するだけです。

HTTPからのリクエストでスクリプトが動かせるようにhttp-wrapperを利用します。 Dockerfileは以下の通り。中で使ってるスクリプトこちらです。

FROM google/cloud-sdk

RUN apt-get update && apt-get install -y openjdk-13-jdk-headless jq && apt-get clean && rm -rf /var/lib/apt/lists/*
ENV PATH /usr/lib/jvm/java-13-openjdk-amd64/bin/:$PATH

RUN mkdir -p /app
WORKDIR /app

RUN curl https://storage.googleapis.com/shared-artifact/hwrap -o hwrap && chmod a+x ./hwrap
ADD parse.sh ./
ADD transfer_gcs.sh ./
ADD load_bq.sh ./
ADD run.sh ./

CMD ["./hwrap", "-p8080", "./run.sh"]

ではCloud Runにデプロイします。

$ GCP_PRJ_ID=$(gcloud config get-value project)
$ gcloud builds submit --tag gcr.io/${GCP_PRJ_ID}/jfr4bq
$ gcloud beta run deploy --image gcr.io/${GCP_PRJ_ID}/jfr4bq

これで準備はOKです。試してみましょう。

$ watch --interval 60 ./transfer2gcs.sh /var/log/myapp/jfr_logs

BigQueryにJFRの情報が準リアルタイムで連携されるのがわかります。

f:id:pascal256:20191209165741p:plain

まとめ

半分くらいJavaではない話を書いた気もしますがいかがだったでしょうか?

リアルタイム分析のところはさておきカスタムイベントとBigQuery/Metabaseでの分析はかなり使いやすいかと思います。 JDK9での仕様の標準化とJDK11でのOpenJDKへの取り込みでこう言った事がやりやすくなったのは非常に良いことかな、と思っています。

MicroprofileのMetricsやOpenTracingなど情報をとる仕組みも標準化してきたので、上手くこの辺りをJFRにも書き込んでWeblogic並みの障害分析環境を標準化出来ないかも今後チャレンジしてみたいです。

それだは、来年もみなさんとJavaにとって良い年でありますように。Happy Hacking!

参考

そもそもJWTに関する私の理解は完全に間違っていた!

TL;DR

  • ステートレスなJWTはそもそもセッションの代替では無い
  • アクセストークンとしての利用が基本で数分レベルの短寿命な有効期限で利用
  • 従来のセッションに近い概念はリフレッシュトークン。てか、リフレッシュトークンはセッションでも(たぶん)良い
  • ユースケース的にモノリスには不要。SPAでMSAな時にメリットが出て来る。

はじめに

Webで認証システムといえばセッション! と言う感じのレガシーおじさんなのですが、最近はJWTとかも出てきてとても気になっていました。新しいものは使ってみたくなりますよね?

なので以前軽く調べてみたたのですが「JWTは危ない」とか「JWTをセッションに使うな」的な記事が大量に出て来ます。

co3k.org

qiita.com

一方で反論記事もあり色んな議論が渦巻いています。そもそも利用を推奨する記事も多い。

auth0.hatenablog.com

この辺りで「JWT怖い!」となり、状態を結局サーバで管理しないとセキュアに使えないならメリットは一体? とも思って一旦放置してたのですが「リフレッシュトークンとアクセストークンに分けて考えるのが前提」と言うのを下記の記事で理解してスッキリしました。

auth0.com

といわけで、私ので理解があってるかの確認がてらまとめてみました。

たぶん私と同じくユースケースが理解出来てなくてイメージが出来なかったり過った利用方法をする人も居ると思うので、そもそも何で必要なの? ってところを重点的に書いてみました。

実際にJWTをまだ使っては無いので理解が過ってるところがあれば指摘いただければと思います。

そもそもセッションにJWTを使って良いの?

「ダメです」

特に、JWTのメリットとして語られるサーバサイドで状態管理しなくて良いから云々でセッションの代替として使うのはアンチパターンと思われます。

そもそもセッションとは状態管理です。その時点でステートレスなインフラを作るのは難しくクライアントサイドでの管理はリスクがあります。幻想は捨てましょう。

ただし、JWTをアクセストークンとして使う事で今まではセッションで実現していた事の一部が代替できます。それは「認証」です。

そもそもセッションがなぜ必要?

さて、そもそもセッションはなぜ必要でしょうか? Webで言う「セッション」とはステートレスなプロトコルであるHTTPにサーバサイドで状態を持たせる仕組みの総称です。

元々は論文などのドキュメント公開の仕組みとして作られたHTMLとHTTPは状態が必要に無いのでその仕組みは存在しません。しかし、ひょんな事からWebアプリケーションとして様々な事に利用されるようになっため状態を持たないと都合が悪いケースが増えて来ました。

その流れでクライアントサイドに情報を保存するCookieなども登場し、クライアントとサーバで共通のキーを持ちそのキーにサーバサイドで情報を紐付ける「セッション」が実現できるようになりました。

f:id:pascal256:20191103131008p:plain

「セッション」を使う事でユーザがログインしているかを判別してユーザ毎の情報を表示するなどの状態をベースにしたWebアプリケーションが作れるようになりました。

セッションのストレージには色んなものが試されていて「NFS + ファイル」「RDB」「インメモリ」「KVS」とかありました。現在の主流はKVSでしょう。JavaEE系はインメモリも現役ですね。

歴史があるだけあってこのCookieとサーバサイドのストレージを使ったセッションはセキュリティ的にも運用的にもかなりの積み重ねがあります。そのため安心して使えるのですが問題ももちろんあります。

ステートレスアプリケーションとスケールアウト

サーバが3台とか5台くらいなら割となんでも良いのですが、サーバをスケールアウトして30台とか100台でWebアプリケーションを実現しようとすると、セッション用ストレージのインフラを作るのが結構骨です。

その流れで普及しだしたのがステートレスアプリケーション。RESTとかROAとセットで語られることが多かったと思います。

ざっくりと言えば「セッションを使ったアプリケーションはスケールさせるのが大変だから情報をクライアントに持たせて全部リクエスト毎に付与しよう」と言う考え方です。

これならサーバに状態を持たなくて良いからストレージがSPOFやボトルネックになる事も無いわけです。なので基本的な考え方になって行くわけですが、この考え方がだと送られてくるデータを全部信用するしか無いわけです。

そもそも渡されるu_idを自由にクライアントが指定できるのでなりすまし天国です。なので、バックエンドAPIとしての利用が限界でそれも「信頼されたアプリケーションから送られて来た情報は信頼する」と言うのが前提となっているので少し弱い。

マイクロサービスとSPA

色々あってマイクロサービスが流行ります。これをサーバサイドでアグリゲーションして一つのWebページとして返してる分にはあまり問題が無いのですが、SPAとかでクライアントサイドでアグリゲーションする場合は問題です。

流石にクライアントサイドからの情報を無条件に信用するステートレス実装はできません。なので、セッションなどを使ったステートフルな実装が基本になるかと思います。その場合はセッションのチェックが1ページ見るのに5個も10個も走る事になります。

今時はインメモリ実装かKVSなのでそこまで一つ一つは大きなコストでは無いですが無視できるものでも無いはずです。特にマイクロサービスだとインメモリは不可能なので必然KVSになります。負荷の集中もそうですが、マイクロサービスなのに共通のストレージにアクセスするのは正直筋の悪い設計となってしまいます。

f:id:pascal256:20191103133958p:plain

この辺はエアプなので想像ですが、たぶんこう言う問題は起こるはずです。

ソーシャル・ログインとAuthorizationプロトコル

少し違う文脈としてソーシャル・ログインの台等とそれを支える技術であるOpenIDから始まりOAuthを経てOIDCに辿り着いたAuthorizationプロトコル群があります。

以下のブログでも書きましたが、もはや自前で認証機能を作るのはリスクの観点で可能な限り避けるべき事なので、GoogleTwitterといった信頼できる認証システムをOAuthやOIDCで使うのが一般的です。 koduki.hatenablog.com

これらの認可プロトコルでは必然的に認可の基盤と実際に利用されるアプリケーションが異なります。その為ものすごく素朴に実装すると毎回認可サーバに問い合わせます。

これはインターネット越しだとそれなりのレスポンスになるはずなのでなんとかしたい。。。と言うわけでこの手のプロトコルの仕様を決める際に寿命の長いリフレッシュトークンと短いアクセストークンに分ける考え方やステートレスに認証できるJWTも作られた見たいです。

リフレッシュトークンとアクセストークンを利用した認証

さて、前置きがとても長くなったけどようやく本題のリフレッシュトークンとアクセストークンのお話。

マイクロサービスとSPAのところでした説明した図を思い出して欲しいのですが各サービスではクライアントの情報は信用できないのでサーバサイドで管理する必要があリマス。逆転の発想で「クライアントからのリクエストが信用できればサーバでの状態管理は不要」となります。

「ユーザID」や「どの機能アクセスできるか等の権限」といった情報を信頼できるサーバで秘密鍵により電子署名を付与する。APIサイドでは公開鍵を使って改ざん検知をするだけで渡された情報を信用できるのでストレージにセッション情報を保存する必要はない。これがアクセストークン。

では、信頼できるサーバとは何か? そもそもクライアントで秘密鍵を使って署名を付けてもオレオレ証明書なので信頼性はない。なので、第三者の認可サーバを使う。ここでユーザIDとパスワードだとかMFAだとか良い感じにユーザを認証してその証明となるキーを発行する。これがリフレッシュトークン。

リフレッシュトークンを持ってるクライアントが認可サーバに必要な情報を渡して署名付きのアクセストークンを作るという流れです。

f:id:pascal256:20191103154050p:plain

JWTは署名付きで任意のペイロードを持てるのでステートレスなアクセストークンの実装に最適という話。そしてリフレッシュトークンはステートフルが要求されるのでJWTを使う意味はあまりないはずです。

自分で実装するならリフレッシュトークンはトラディショナルなCookie + サーバサイドなセッションがWebアプリは一番楽かもと思っています。

JWTとセキュリティ

さてステートレスなJWTは実際セキュアだろうか? これは実装次第だけど基本的には問題ないと思います。

当たり前ですがJWTであるアクセストークンは改ざんチェックしかしてないので本人認証としては弱い。なんらかの問題で流出したらアウト。秘密鍵を変えるか有効期限が過ぎるのを待つしか無いです。

たとえリフレッシュトークンを使って再発行しても古いアクセストークンをステートレスに無効にすることは出来ない。やるならブラックリストデータを状態として持つしかない。

なので理想的にはアクセストークンはワンタイムトークンとして振る舞うのがセキュリティ的には良いと思います。仕様上それは出来ないから1分以下とか超短期の有効期限にするのがベスト。

これならアクセストークンが流出してもあまり問題はないはず。リフレッシュトークンはサーバサイドで状態を持ってるので万一流出したら破棄することが出来るので通常のセッションと同程度の長さで問題はないし明示的なログアウトも作れます。

ただし、アクセストークンの有効期限が短いと認可サーバへの問い合わせ回数が増えるのでここはトレードオフですね。認可サーバの負荷的には従来のセッションによると大差無い気もするけどクライアント側の通信コストが問題。ユーザが多いと署名コストもボトルネックになるかもです。

この辺のさじ加減は作ってみないと分からないのでバランスはサービス次第になるかと思います。

JWTはセッションの代わりになるのか?

なりません。

セッションが担っていた一部である認証に関しては限定条件で効果的に果たせそうです。ただ、そもそも状態の保存ができません。「ペイロードに格納すれば良いじゃん?」って話ですがそれは都度都度アクセストークン発行すれば出来ますが、たとえばAmazonの買い物カゴみたいなのをJWTで作るのはかなり面倒では。

その場合は大人しくCookieやLocal Storageに格納してAPIのBodyに入れるのが良いかと思います。暗号化もサポートしてるけど多少センシティブなものも突っ込めそうですが、ストレージとしての用途を考えて作ってるとは想定しづらいのでセッションの単純置き換えは無理でしょう。この点に関してはそう思ってる人も多分居ないと思いますが。

まとめ

さて、思ったより長かったけどJWTを使った認証の流れとユースケースを私なりにまとめてみました。

何というか従来的なモノリスなアプリケーションだと何ら意味が無いという事が分かった。

セキュアに作るならステートフルなリフレッシュトークンとステートレスなアクセストークンにする必要があり、単独アプリならステートフルなトークンすなわちセッションだけで十分だし。ソーシャルログインとか別の仕組みの一部として使う分には別だけど。

ここを理解しないとJWTをセッションに使おうという発想になってしまうのだろうなぁ。とは言え私の今の理解が正しいとも限らないけど。認証周りは本当にややこしい。。。

それではHappy Hacking!

参考

Graphvizで画像を読み込むとエラーになる

Graphvizで画像を下記の構文で読み込もうとするもビルド時にエラーが発生。

d [shape=none, label="", image="sample.png"];
$ dot -Tpng -o sample.png sample.dot             Wed Oct 30 23:03:17 2019
2019-10-30 23:03:18.988 dot[97555:3328824] +[__NSCFConstantString length]: unrecognized selector sent to class 0x7fff9f6133a0
2019-10-30 23:03:18.988 dot[97555:3328824] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[__NSCFConstantString length]: unrecognized selector sent to class 0x7fff9f6133a0'

どうもbrewでインストールしたGraphvizにlinbpngとかが適切にリンクされてないのが原因っぽい。と言うわけで以下のコマンドで再インストール。

$ brew uninstall graphviz
$ brew install pango librsvg
$ brew install graphviz --build-from-source

これで無事画像が表示されるようになる。