TECH PLAY

アプトポッド

アプトポッド の技術ブログ

252

aptpod Advent Calendar 2022 1日目の記事です。 みなさまお久しぶりです。アプトポッドで人事をしている神前(こうさき)と申します。 前回の登場が 去年のAdvent Calendar なのでちょうど一年ぶりくらいでの登場となります。 今年もAdvent Calendarの一発目を飾ることになり戦慄してますが宜しくお願いいたします。 今年もCTOからの依頼に答える、の図 今年もあっという間に年末になり、詳細な振り返りはおそらくCTOの梶田が記事にすると思いますが、私は私で激動の1年でした。 簡単に自分の1年を振り返ると 昨年末:経営層から大量採用のオーダーがきて戦慄 12~1月:大量採用のための準備(RPOなんかも初めて使ってみた) 1~4月:ひたすら面談面接の日々(おかげで採用自体はかなりすすんだ) 5月:会社の方針転換に伴い採用活動を一旦停止 6~11月:ワークエンゲージメント活動のような内部施策を実施 12月:事業部に異動 うーん、こう書き出してみてもなかなかのものだなと改めて思います。 さて、今回のブログテーマなのですが、昨年と同じテーマでやっても芸がないので、今年の後半に取り組んでいたワークエンゲージメント活動について振り返りも兼ねて書いていこうと思います。 先に結論を書いておくと、半年間取り組んでみて、学びやよかったこともあるものの、反省する部分も多かったです。あんまりきれいなことを書くというよりかは、「内部施策についての素人が、ない知恵を絞りながら取り組んだ悪戦苦闘の記録」として書いていきたいと思います。これからワークエンゲージメントについて取り組むぞ、みたいな人事の方がもし読者の中にいたら参考になると幸いです。 そもそもワークエンゲージメントとは 自分が担当をするようになった当時の状況 運用側のサイクル 実施実績 数値の変遷 アンケートの変遷 ワークエンゲージメントについて調査をしてわかったこと サーベイフィードバックミーティングについて よかったこと 反省点 まとめ 参考文献 そもそもワークエンゲージメントとは 日本で「ワークエンゲージメント」についてにわかに注目が集まったのは2017年前後になります。きっかけはおそらく こちらの記事 のような「熱意あふれる社員」の割合が日本では6%しかおらず、調査した139カ国中132位と最下位クラスだったというというギャラップ社の調査結果公表ではないかと思います。 グーグルトレンドで見てみると グーグルトレンドでは2016年の年末ぐらいから検索が急上昇しているので上記の記事とは別の契機があるかもしれませんがいずれにせよこのタイミングからいままで右肩上がりですし、いまだにワークエンゲージメントといえばこの2017年の調査結果が引用されることも多いです。その後、2018年に厚生労働省がだした「 平成30年版 労働経済の分析 -働き方の多様化に応じた人材育成の在り方について- 」第Ⅱ部 第3章 第1節内のコラムにて「ワーク・エンゲイジメント」が多様な人材活用と雇用管理の文脈でとりあげられ、翌2019年の「 令和元年版 労働経済の分析 -人手不足の下での「働き方」をめぐる課題について- 」では第Ⅱ部 第3章をまるっと使って「ワーク・エンゲイジメント」についてとりあげられています。いわゆる働き方改革といった文脈のなかで、労働生産性をあげるにはどうしたらいいかという問いに対する解の一つとして語られることが多いようです。 さて、上記の図をみると、2017年前後からの注目とは別に2004年(グーグルトレンドでは2004年以前は調べられない)あたりでも検索をされていることがわかります。それもそのはずで、「ワーク・エンゲイジメント」は最近できた概念ではなく、1990年代ごろからビジネス領域(やコンサル領域)では徐々に使われ始めており、上記の厚労省で引用されているような「ワーク・エンゲイジメント」の概念は2002年にユトレヒト大学のシャウフェリ教授の論文によって提唱されているからです。近しい概念といってもよい「ワーク・モチベーション」などは1950年代にすでにでていたりもするので、概念の歴史自体は実はかなり長いのです。 では、「ワークエンゲージメント」とは一体何なのかというと、少し乱暴なまとめ方にはなりますが、「従業員が職場や仕事に対して熱意をもって取り組む状態」であり、ワークエンゲージメント施策とはつまり「そうした状態にするための環境作りや施策、取り組み」といえるかと思います。ワークエンゲージメントを計るための方法はいくつかありますが、代表的なのは日本でも注目されるきっかけとなったといえるギャラップ社が開発した「Q12」や、上述したシャウフェリ教授の「UWES(Utrecht Work Engagement Scale)」等があります。計測方法やその尺度に違いはありますが、いずれにしてもワークエンゲージメントが高ければその会社の業績に対してポジティブな影響があることが様々な調査によって分かっています。少子高齢化が進む中で、いかに一人ひとりの労働生産性を高めるか、という文脈の中でワークエンゲージメントは近年特に注目をされているものといえるでしょう。 自分が担当をするようになった当時の状況 さて、そんなワークエンゲージメントへの取り組みについてですが、自分が担当をした時(6月ぐらい)に0からまっさらのスタートというわけでは実はありませんでした。というのも、昨年の11月ごろにも一度エンゲージメントサーベイ(サーベイ=アンケート)を実施していたからです。当時はギャラップ社のQ12を利用してアンケートの実施、回答結果をまとめて1月からいくつかの施策につながっています。ですが、この時は自分は基本的にノータッチでした。そこから約半年を経て再開するにあたってまずはどういう風に進めていくかをマネージャーと簡単に話して以下のことを決めました。 アンケートのフォーマットはQ12を引き続き利用する:前回と同じフォーマットを使うことで差分をだしたい アンケートの頻度は月1とする:いわゆるパルスサーベイ的に細かく数字の変化を追えるようにしたい アンケートの文言を調整する:Q12の設問は固いものも多いので回答しやすいものに調整をしたい(例:Q10の「職場に親友はいますか?」について「親友とは?」となったり) アンケートの後にはサーベイフィードバックミーティングを実施する:アンケートをとって終わりではなく、それをベースに議論をして改善点があればなにかしらのアクションにつなげたい サーベイフィードバックミーティングはQ毎に実施する:毎月やるとさすがに現場の負担を大きいので 後述しますが、実施の段階でもっとよく調査をしてからスタートすればよかった、となるのですが(少し詳しい方からみたらQ12はパルスサーベイ的に用いるものではないということはお分かりかと思います)、当時は社内にもこうした取り組みの経験があるメンバーがいなかったこともあり、まずはデータをとるところから進めていこうとなった次第です。 運用側のサイクル 弊社では隔週で「全体会議」と呼ばれる会議体がありまして、オンラインで全員が集まり、事業進捗の報告や入退職者の挨拶、プロダクトのリリース情報共有等がされています。ほぼフルリモートに近い働き方(オフィスへの出社率はだいたい10%前後)になっているため、全社員が集まるこの「全体会議」を軸にワークエンゲージメント活動はサイクルを回していました。具体的にいうと、だいたい月に2回ほど全体会議があるので、月の前半の全体会議にてワークエンゲージメントアンケート実施の告知、翌週を期限一杯としてアンケート回収、月の後半の全体会議にて結果を共有、その次の全体会議にむけてアンケートの準備、というサイクルです。ちなみに集計についてはスプレッドシートを利用し、集計された数字を関数でひっぱってきてそれぞれの部署やグループごとにまとまるようにしました。ある程度数字をひっぱって自動化はしていたのですが、とはいえ手作業になる部分も多く、毎度ここに時間を使う部分もありました。サーベイフィードバックミーティングを実施する場合はアンケート結果を共有したあとに同時並行で実施をしていく、という感じです。 実施実績 そんなこんなで活動実績としては以下のようになっています。 7月末:(再開して1回目の)アンケート実施 8月:サーベイフィードバックミーティング実施 9月:アンケート2回目実施 10月:アンケート3回目実施 11月:アンケート4回目実施 11~12月:サーベイフィードバックミーティング2回目実施(予定) おおよそ当初の予定通りにサイクルとしては進みましたが、初回はアンケートの実施、まとめ、サーベイフィードバックミーティングの実施でまごついてしまい時間を使ってしまいました。それ以降は順調にサイクルとしては回せたかなというところです。 数値の変遷 弊社の組織構造としては部(もしくは室)の下にグループという構成になっています。アンケートでは匿名回答とし、グループを最小単位としてまとめてありますが、組織によってはグループがないところもあります。個々のグループの数値の変遷をだすことはさすがにできませんが、全社での数値の変遷としては下記のように推移しています。 見づらいのはご容赦ください 昨年の11月時点から数値としては徐々にあがり、直近では少し下がっていることがわかります。これらはあくまで全体をならした数値なので、各部や各グループではもちろんばらつきがありますが、全体ではおおよそ4.0付近を推移しています。数値の上下についてはその時々の会社の状況の影響もあるので一概に言えない部分はありますが、いくつかの部分についてはアンケートそのものに手を入れた影響があると考えています。 アンケートの変遷 7月から再開するにあたってアンケートの文言調整をしました。なぜかというと、昨年に実施した段階での文言はほぼネットに転がっているような文言をそのままもってきていたため、どう回答をしていいかわからないという声があったからです。また、当時は各設問の背景や補足の文言もなかったため回答をする人の解釈にかなりの部分を委ねる形になっていたという部分もあります。 (2021/11実施時) 例: Q1 職場で⾃分が何を期待されているのかを知っている Q4 この1週間のうちに、よい仕事をしたと認められたり、褒められたりした Q8 会社の使命や⽬的が、⾃分の仕事は重要だと感じさせてくれる Q10 職場に親友がいる とりわけ上記の図にもある通り、Q10の「親友」をどう受け取っていいのかはわりと困惑する部分であり、それが数値としても顕著にでています。 他にも「これどう解釈すればいいんだろう?」という部分があったため、まずは回答をしやすいように文言を調整しました。 (2022/7~10実施時) 例: Q1 職場で⾃分が何を期待されているのかを知っている(理解している) Q4 直近1週間で自身の担当をした業務に対してポジティブなフィードバックをもらいましたか? Q8 会社のやろうとしていること(=ミッションやビジョン)に自分も貢献をしたいと感じますか?あるいは会社のやろうとしていることに対して自身も共感しますか? Q10 会社に、仕事以外でも付き合える、あるいは付き合いたいと思える人はいますか? いまから振り返れば汗顔の至りなのですが、当時は「もう少し自然な感じの質問にしよう。もう少し解釈の幅を狭めて回答しやすくしよう」と考え調整をしました。その結果といってもよいのか、7月実施の際は前回(2021年11月)実施時よりも全体的に数値が上昇をしました(Q10は特に顕著)。 こうした文言の調整の他にも、追加の質問をその時々で設定し、例えば10月実施時には「フィードバック」にフォーカスをしてより詳細な質問をしていたりもしています。 そんな折、10月実施時に弊社のVPoPである岩田から「Q12の原文はこうなので、いまの設問はちょっとそこからずれているように感じる。こんな感じにしてみてはどうでしょう?」と提案がありました。自身としてもQ12の各設問は具体的にどういうことを聞いていて、どういう背景でそもそもQ12があるのか、この数値をどう解釈していくといいのかという行き詰まりのようなものを感じていたところがあったため、一念発起して「そもそもワークエンゲージメントはどういうものか」の調査を開始しました。そこで新たにわかったものは後述するとして、調査の結果をまとめたものをアンケートとともに展開し、アンケートの文言を再調整して実施したのが11月の実施回となります。元々のQ12の原文に近い形に調整をしつつ、各設問で聞きたいことを補足として付け加えながら元来の設問意図にしっかりと沿うようにしました。 (2022/11実施時) 例: Q1 職場で自分が何を期待されているのかを知っている Q4 直近1週間で自身の担当をした業務に対して褒められたり、認められたりといったポジティブなフィードバックをもらいましたか? Q8 会社の使命や目的が、自身の仕事は重要だと感じさせてくれますか? Q10 職場には(それが友達か親友なのかはさておき)安心して背中を預けられる人はいますか? それ以前の設問は「回答のしやすさ」を意図していたこともあり、無意識的にも「数値が高くなりやすい」ものになっていたと今では思うところです。それを元来の形に近いものに再度調整をし、さらにQ12についての調査結果をまとめたものを展開したことにより、それを読んだ人からは「これはこういうことを聞いていたのか。であればここは前なら(例えば)4だったけど、それなら3だな」というような感じになったのではないかと思います。その結果として11月のアンケートではそれまでとは数値が微減したのではないかと考えています。施策の実施者としては当然数値が高いほうが喜ばしいわけではありますが、その一方で数値がみせかけのものであっても意味がありません。数値としては下がったものの、より会社組織全体の実態に近い形に数値としてはなったのであればその方が良かったと思います。 ワークエンゲージメントについて調査をしてわかったこと ワークエンゲージメントとはそもそも何ぞやというのを調べ、まとめをつくったわけですが(今調べたらだいたい10,000字ぐらいあった)、内容としては ワークエンゲージメントが注目された時期 厚労省での定義 アカデミック文脈でのワークエンゲージメント ビジネス文脈でのワークエンゲージメント Q12とは 各設問の背景 という感じでまとめました。 実際のまとめの冒頭部分 本稿ではQ12の詳細に触れませんが(さすがに字数が膨大になる)、かなり学びがありました(詳細に知りたい方は文末の参考文献をどうぞ)。また、Q12を含む「ワークエンゲージメント」周辺の概要についても知ることができたことも学びになりました。広く言えば「組織開発」に該当する領域だとは思いますが、これまで採用業務を中心にしていた自分としては新鮮でした。 Q12に多少焦点を当てると、例えば「Q12の設問は4つの固まりに分けることができ、全体として連関している」ことも今回の学びの一つです。言われてみれば当たり前のことではあるのですが、実際にアンケートへの回答する時にしかQ12に触れない場合、それぞれの設問がどうつながっているかはやはり見えにくいものです。 Q12の全体像 あるいは歴史や成立背景を知ることも理解を深めるのにとても役立ちました。元々はアメリカの調査会社であるギャラップ社が膨大な調査結果から導き出したものがQ12であるのに対し、例えば厚労省がとりあげているワークエンゲージメントはJD-R理論を背景としていますし、もっと広く言えばポジティブ心理学という「心理学」をベースにしています。ネット上にある「エンゲージメント」についての記事の中にはこのあたりがごっちゃになっているものもあり、それぞれの背景を知らないと、活動として実施するにしても見るべきものを誤ってしまう可能性があります。 細かいところで言えば、例えばQ2では「仕事をうまく⾏うために必要な材料や道具を与えられている」が聞かれるわけですが、ここでいう「材料や道具」はPCやデスクといった物理的なものに限らず、「業務を進めるにあたって必要な情報」や「必要な情報にアクセスできるかどうか」といった非物理的なことまでが含まれます。設問の「文言だけ」をみて「材料や道具」を物理的なものだと認識した場合、「PCは最新のものだし性能もいい、デスクやディスプレイもいいものを使える。でも情報についてはあんまり上から降りてこなかったり、どこにあるかよくわからないことがあるなぁ」という人は「5」や「4」と回答する一方で、「材料や道具」を非物理的なものまで含むと正しく認識をしている場合であれば「3」やそれ以下で回答する可能性はあるでしょう。こういった感じで、細かいといえば細かいのですが、設問が聞いているものを正しく伝えて、それを理解してもらった上で回答をしてもらうことでより実態に近い数値に全体として推移するものと思います。 設問に対する補足部分 サーベイフィードバックミーティングについて 話はガラッと変わり。8月にはその前月にとったアンケートをもとにサーベイフィードバックミーティングを実施しました。実施に際して、実施を前提とした上でまずは各部や室の部門長に実施方法の打ち合わせをしました。打ち合わせ内容としてはざっくりと下記になります。 結果の共有:各部門/室単位での数値結果の共有 実施の有無:アンケート結果をもとに実施の有無の判断 実施単位:グループによっては人数が少ないこともあるので他のグループと合同で実施するかどうか、等 上記をすり合わせたあとに、それぞれのグループマネージャーとより詳細に下記を詰めていきます。 実施タイミング:各グループでの定例があればそこで時間をもらう。難しければ別途時間を設ける。 実施時間:他の業務都合も鑑みながら30分~60分の間で調整 実施の際は以下のように進めました。 冒頭挨拶(3分):サーベイフィードバックミーティングの目的とおおまかなルール説明 結果の共有(3分):そのグループの数値結果を画面共有 議論の時間(残り時間):自身がファシリテーターとして話をふっていく 議論といっても侃々諤々でやるというよりかは、まずは数値結果をみながら率直にどう思ったかを聞いていくような感じで進めます。何かしらのアクションにつながるようなところまでいけるのがベストではありますが、無理にそこまでを目指さずに、また、話題としてもポジティブなことからネガティブなものまで含めて広くヒアリングをしていくように意識しています。 議論を進めながら議事録をつくり、終わった後にメンバーに共有をして終了となります。 よかったこと まだまだ道半ばではありますが、振り返ってみての(会社視点というよりも個人視点ですが)よかったことを書いていきたいと思います。 組織に対するもやもやが可視化された:月一でアンケートを実施したことにより、数値の変化を追えるようになったのはやはりよかったことだと思います。これが年一や半年に一回だと数値の変化を追いにくく、また、その変化の原因を絞るのが難しかったと思います。「なんとなく空気がよくない気がする/よくなった気がする」というのが、主観による思い込みではなく客観的なデータとして数値で表れるので、それをベースに議論を始めるのはとてもしやすかったように思います。また、アンケートではフリーコメントの欄も設けているので、そこでの回答内容も踏まえて、例えば「この施策があったことでこの数値はこう変化したと思います。実際にこういうコメントもありました」という会話がしやすくなったと思います。ただし、可視化はあくまでスタートで、そこからどうアクションにつなげていくか、というのがより大事な部分ではあると思います。 より現場を知るきっかけになった:アンケートのあとにサーベイフィードバックミーティングを実施することで、いろいろな会話をするのですが、そのなかでの気付きというのは多かったと感じています。自分は元々採用をしていたので、現場のみなさんと会話をする機会自体は多い方でしたが、「採用」とは違う軸での会話、さらにいえば「組織課題」であるとか、もっと現実的に「実はこういうところに困っている」といったような話も聞けたのはよかったと思います。特に弊社はフルリモートに近いため、オフィスにいればなんとなく感じれるかもしれないものが見えづらく、どういう風に業務を回しているのか、どういうことを実際にやっているのか、その中で何に困っているのか、というのはどうしても察知しにくい面があります。さらに、「組織をどうよくしていこう」みたいな会話を正面切ってする機会もそもそも多くはないなかで、普通に接しているだけではなかなか聞けないことを聞けるとても貴重な機会だったと感じています。 反省点 よかったこともありましたが、当然反省点もあります。手探りだったところもあるので色々反省点はあるのですが、以下に大きく2つほど反省点を書いてみようと思います。 課題意識を明確にする:今回自分は半ば引き継ぎのような形で担当になったわけですが、「どういう目的で」、「何が課題で」、「そのためには何をすべきか」という点を事前にしっかりと詰めれていたわけではありませんでした。今回調べてわかったように、一口にワークエンゲージメントといっても、計測方法も、その背景にある理論も様々です。今ある組織の課題が何かというのをしっかりと議論をし、明確にすることで、それにあった運用方法、計測方法を選択すべきだったと今では感じています。もちろん、だからといってこれまでの活動が無駄ということはなく、例えばパルスサーベイ的な運用をしたことにより細かい数値の変化を追えたので、「この変化はこういうことがあったからかな」といった推測も立てやすく、組織全体としての変化の影響もポジネガ両面で見ることができたりもしました。また、課題ありきで進めるのではなく、「課題があるかを探るためにまずはアンケートをとって現状把握をしてみよう」というのも考え方の一つだと思います。ただし、その場合は「データをいつまで取るのか」といった期限を設けるとよいと思います。 頭でっかちになりすぎない/アクションまでつなげる:ワークエンゲージメントに限らずですが、いわゆる管理部門がなにかしらのアンケートを取る時に、その結果がどこにどう反映されてるのかがよくわからない、ということは往々にして発生しがちだと思います。今回でいえば、アンケートとサーベイフィードバックミーティングをセットで運用をし、アンケート結果を振り返りながら現場の皆さんと一緒に議論をするところまでは一応実行できたかとは思います。ですが、もっと踏み込んで、例えば数値の変化をもとに各マネージャー陣と会話をし、一人ひとりのメンバーのケアに活用をするといったようなところ、より具体的にアンケートをどう活用していくのかというところまでできればよかったと今では思うところです。 まとめ いかがだったでしょうか。読み返してみてもなかなかの手探り感だったと思います。冒頭でも書いた通り、私自身は今月から部署が異動になるため、ワークエンゲージメント活動に直接的にかかわることは今後ないとは思います。ですが、取り組み自体は引き続き管理部として継続していきますので、今後も間接的に関われればと思います。 こうした人事施策はすぐに結果が出るものではなく、また、目に見えてわかりやすい結果が出るようなものでもないため、粘り強く取り組んでいくしかありません。私自身、アンケートのフリーコメントにあった励ましのコメントで喜びつつ、一方で「これって何のためにやってるんですか?」といったコメントで凹みつつを繰り返しながらやってきました。積極的にこうした取り組みに協力してくれる社内のメンバーはもちろんのこと、「面倒だなぁ」と思いつつも協力してくれるメンバーの皆さんの協力にも感謝です(ネガティブなコメントもとても大事)。みなさんの協力がなければそもそも何もできませんでした(毎回アンケートの回答率は90%以上なのはすごいこと!)。ありがとうございます。 そして、もし今後「うちでもワークエンゲージメント活動やってくぞ!」みたいな方がこれを読んでいる方の中にいて、その方にとって少しでも参考になれば幸いです。 それではまた1年後にお会いしましょう。 参考文献 Q12関連: 『 これが答えだ!: 部下の潜在力を引き出す12の質問 』:Q12を作成したギャラップ社の社員が書いた書籍。Q12それぞれについて詳細な説明があるので把握をするのにはこれがよい。 『 まず、ルールを破れ: すぐれたマネジャーはここが違う 』:同じくギャラップ社の人が書いた本。Q12作成の歴史みたいな側面があるので、Q12の成り立ちから知りたいならこちらもおすすめ。 『 ストレングス・リーダーシップ<新装版> さあ、リーダーの才能に目覚めよう 』:Q12をマネージャーや経営層にフォーカスして書かれたもの。Q12をどうマネジメント視点で活かすかについて知りたい方はこちら。 サーベイフィードバック: 『 サーベイ・フィードバック入門――「データと対話」で職場を変える技術 【これからの組織開発の教科書】 』:サーベイフィードバックに関してはこの本をだいぶ参考にしました。著者の方は組織開発系の書籍も書かれているのでそちらも興味ある方は探してみてください。
日々使う機器 小物類 終わりに パパエンジニアの週末(おまけ) EDGEPLANTグループの平野です。2021年4月にハードウェアエンジニアとして入社しました。 ハードウェアエンジニアは、お客様の要望を具体的に聞き、どう実現するのかを考えます。 必要な機能・費用・工数を算出して、部品の選定、製造の各工程、守らないといけない規格、製品の完成形などをふまえて提案・設計・製作などを担当します。 入社して1年半ほど経ちますが、今回、Tech Blogに何を紹介できるかかなり悩みました。 グループに相談したところ、同僚の野本さんに「仕事柄、色々な大きい機器を使っているイメージですが、リモートではどうしてるんですか?」と聞かれました。 確かに、仕事では色々な機器を使いますし、会社にはEDGEPLANTグループの様々な機器を置いている"島"もあります。 そこで今回は、ハードウェアエンジニアのリモートワーク環境について、私の例をご紹介します。 日々使う機器 さて、ハードウェアエンジニアの仕事で必要不可欠な機器は四つあります。 オシロスコープ(略称オシロ):製品の機能や様々な現象・疑問がある時、様々な信号の波形・タイミング等の確認につかいます。年中必要です。 マルチメーター:基本大きな電源は要らず、どんな場所でも部品・信号を簡易に確認できて、既に所持しています。 安定化電源:不確定な要素(ノイズ)を排除して、幅広い電流と電圧を供給できます。回路の評価・デバッグに役に立ちます。 半田ごて:基板の配線・修理等に使います。 作業場の風景 コロナ禍で入社してすぐ、リモートワーク用にそれらのすべての機器の支給を提案されました。 そこで、私は半田ごて1式だけ購入させて頂きました。 実は、他の機器は、前職の会社が会社をたたむことになって自分も辞めることになったとき、 ほぼ自分一人が使用していた機器類を譲ってもらったものを愛用しています。 その中に、特にオシロは2・3回修理しまして、愛着があります。20年前の測定器ですが性能は十分現役です。 他に、一人に使うには“贅沢”とも言える電流プローブと信号発生装置を所持しています。 電流プローブは基板のデバッグ、ノイズの解析等、電流の波形には情報豊富で大変役に立ちます。 信号発生装置は今の所出番が少ないですが、アナログ信号関連で使用しています。 リモートワークのために自宅に用意している機器は以上です。 会社には、ここまで紹介したような機器はもちろん、大掛かりな恒温槽、スペアナ等があります。特殊な測定器が必要な場合、レンタルして会社に設置することもあります。 製品の正式な評価では会社の機器やレンタル機器を使うために出社する必要がありますが、検証・デバッグは自宅の機器で行えるので、普段のほとんどはリモートで仕事ができています。 小物類 ここまでご覧になって、皆さん意外に多いと思うのではないでしょうか。 ところが機器が多いだけでは収まりません。 このほかに評価ボード・道具・部品のサンプルというような小物がかなりあるのです。 評価ボードは新しいプロジェクトを始める度に増えてしまいます。 道具は電化製品の修理という趣味もあって、基本衝動買いです。 抵抗・コンデンサ・ダイオード・変換小基板・ケーブル・ネジ類等々は備蓄しています。 自宅にあるサンプル品は測定器と同様にデバッグ・評価機のみに使います。 正式な製品には会社で管理している部品のみ使います。 部品サンプルのごく一部 リモートで多分一番大変な業務はこれら小物の整理・管理でしょう。でも、豊富な小物がすぐに使える状態になっていてこそ、リモートでハードウェアエンジニアの業務を出来ているんだと思います。 終わりに 元々持っている物も多かったけど、アプトポッドはリモートワークにあたって必要な物品の支給を提案してくれました。 よって、よく使う機器や小物は全て自宅にあるので、問題なくリモートワークできています。 以上、長々と自分のリモート環境について述べてしまいました。 でも、お時間がある時、こちらのおまけ(パパエンジニアの週末)も読んで頂ければ嬉しいです。 最後までご覧くださいまして、ありがとうございました。 パパエンジニアの週末(おまけ)  ある週末、長男がいきなり仕事部屋に入りました。 長男:パパ!今日基板できる? 私:何の基板? 長男:プラレールの基板、自動のやつ。 私:自動ってどんな風に?もっと具体的に説明してみて? 長男:電車は衝突しないように、あと、タブレットで操作したい。 私:タブレット操作は難しいな。まず、衝突しないようにすることに絞ってみようか。 長男:横にぶつからない為には緊急停止、前に電車がある時はブレーキ、減速する… 私:そうだね、電車を止めるのもブレーキをかけるのも、スピードを調整する必要があるね。それで、どうやって前に電車があるのを判断するの? 長男:前に使った音波センサーがある。使えない? 私:超音波センサー?良いかも。前に使ったのは電車にのせるには少し大きいから、もっと小さいものか、別の種類のセンサーを探しておくね。 長男:分かった。あとはいろんな電車に取り付けたい。 私:分かった。なるべく小さい基板にまとめれば他の電車にも使えるね。一応、今までの目的をノートに書いておいて。 長男:こんな感じ 目的1:スピードを調整する。 目的2:遠隔操作をする。無線で操作をする。 目的3:センサーで止まるようにする。 目的4:色々な電車にとりつける。 私:いいね。じゃ、今日は目的1をやってみようか。これはプラレールに入っているモーターと同じです。電池を接続するとこう回る。スピードを上げたい時、どうやる? 長男:パワーを上げる、電池を増やす! 私:学校で勉強したと思うけど、電池はどうやって接続する?並列?直列?接続してみて。 長男:直列!並列は電池を長く使いたい時! 私:大正解!直列に電池を増やせばモーターがより速く回せるの。でも、今回はスピードを上げるのではなく、電車を遅くしたり、止めたりしたいんだよね。どんな方法があると思う? 長男:電気切れば止まるけど、スイッチ? 私:そう!スイッチ。電車のスピードを調整するには、素早くON/OFFを切り替えれば いいの。先のモーターと電池の間、自分でスイッチをON/OFFして、動かしてみて。 …PWM制御をしばらく説明して… 私:どう?さすがに手で調整するには限界があるよね。私達は手で1秒間10回ON/OFF出来ないでしょう?そこで、手動スイッチの代わりに電気的なスイッチを使うための回路を作ります。 私:まず、モーターを動かすにはモーターに必要な大きい電流をON/OFFできるスイッチの代わりの「トランジスター」という部品(コレクター-エミッタ)を置くね。そして、こちら側(ベース)に小さなON/OFF出す機器を入れる。私達の手の代わりになる。信号発生措置、英語で“Signal Generator”、”SG“と書いてある。 私:さっきの電池、モーターとあわせてこんな回路になる。そして今回、モーターが止まったときに高い電圧が出ると他の部品が壊れる恐れがあるので、この部品、ダイオードを追加する。 長男:ダイオード…? 私:ダイオードの説明は別の機会に説明するね。そろそろ、回路を組んでみようか。部品はちゃんとデータシートを読まないといけないけど、今日は手持ちの部品から選ぶので、ちょっと違うやり方をする。 長男:どういうふうに? 私:手持ちの部品の中で大きい電流を流しても壊れない部品を選んでいるの。本来は電流のほかに、小ささ、値段、損失など、考慮するポイントが結構あるんだよ! 長男:わかった! 私:できた!電池の代わりにこの電源(安定化電源)を使ってみる。電圧は電池と同じ1.5Vにした。オシロをみて、黄色波形はSGからのON/OFF信号だ。緑はモーターに流れる電流だ。 私:SGのこれ(Duty)を1から99まで調整できる。1にするとONの信号がとても短い、モーターは? 長男:止まってる! 私:では、このつまみを回して、この数字(Duty)を大きくしてみて。 長男:音が大きくなる、モーターが速くなる!ONが長くなる!どこまで上げて良いの? 私:フルパワーは99、通常は75かな? 周波数25Hz, duty 75% 周波数25Hz, duty 25% 長男:75は通常速、5で止まる、高速は85?75とあまり変わらない・・・じゃ、通常速60、高速85、低速20位にしてみようかな・・・停止が1・・・、でも、カクカク言っていて、なんかディーゼル車みたい。結局止まっているけど電流が流れている?これ0に出来ないの? 私:そう、1でも電流は流れるよ。電流の最大値も大きくなっていない?これは止まっているのに無駄にエネルギーを消費していてよくないね。スピードを決めるとき、その辺、ちゃんと確認しよう。この“ON/OFF”ボタンで完全に停止できる。 長男:分かった! 私:今回はSGからON/OFFの信号を出しているけど次はもっと小さい部品から作らないといけないね。 長男:じゃ、レベル1クリアってこと?次はプログラミングも入る? 私:そう、プログラミングに入る。どんなICでやるのか調べて置くね。今日はここまででいい? 長男:分かった、ありがとう!じゃ、行くね! 私:はい、お疲れ! あれっ!週末、結局仕事をしているじゃないの!?次世代のエンジニアが誕生するなら良いか!
こんにちは。ソリューションプロフェッショナルグループの新崎です。 最近「intdash で収集したデータをユーザー所有のクラウドストレージに転送する」という機能開発を担当しました。ユーザー所有のクラウドストレージにアクセスするためにはユーザー環境の認証情報が必要になります。しかし、認証情報をそのまま受け取ってしまうとセキュリティ対策として管理コストが大きくなったり、ユーザー自身で頻繁に認証情報のローテーションをしていただかなければならなくなったりと、サービスとしてのユーザビリティにも影響が出てしまいます。 今回の開発ではワークロードを AWS Lambda 上に構築していましたので、「ユーザー所有のクラウドストレージ」が Amazon S3 であれば AWS Security Token Service の AssumeRole を利用して一時トークンを発行する方式を採用することで、これらの課題を解決できます。本記事ではさらに一歩踏み込んで「ユーザー所有のクラウドストレージ」が AWS の外、具体的には GCP の Cloud Storage だった場合についても同様の一時トークン方式で実現させる方法をご紹介したいと思います。 Workload Identity 連携 ユーザー環境での準備 事前準備 サービス アカウントの権限借用を許可する権限を外部 ID に付与する サービスワークロードでの処理 まとめ Workload Identity 連携 サービスアカウントキーを使用せずに GCP のリソースへのアクセス権を、オンプレミスまたはマルチクラウドのワークロードに付与するためのサービスとして Workload Identity が活用できます。 Google Cloud のブログ記事 に記載のある以下の手順で設定をしていきます。 GCP プロジェクトで、Workload Identity プールのリソース オブジェクトを作成します。Workload Identity プールは、キーを必要としない連携メカニズムを容易にするために構築された新しいコンポーネントです。このプールは、外部 ID のコレクション用のコンテナとして機能します。 1 つ以上の IdP を Workload Identity プールに接続します。IdP は、OIDC プロトコルをサポートする AWS や Azure のアカウントまたはプロバイダのいずれかになります(SAML もまもなくサポート)。 次の 2 つの IAM ポリシーを定義して、プールにリソースへのアクセス権を付与します。 サービス アカウントに、目的のリソースへのアクセスを許可するポリシー。新しいサービス アカウントを作成することも、既存のサービス アカウントを再利用することもできます。 プールの ID が、サービス アカウントになりすますことを許可するポリシー。これらのポリシーの作成に関する詳しい情報については、ドキュメントをご覧ください。 ワークロードを STS エンドポイントに対して認証し、サービス アカウントになりすまして、目的の GCP API を呼び出します。 ユーザー環境での準備 ユーザー環境(GCP)では以下の手順で設定をしていただく必要があります。 事前準備 事前準備として、GCP のコンソールやコマンドラインツール等を使って こちらの公式ドキュメント を参考に Workload Identity プールとサービス側の AWS アカウントと紐付けたプロバイダを作成しておきます。また、アクセス許可するリソースに対するアクセス権を持ったサービスアカウントも作成しておきます。 サービス アカウントの権限借用を許可する権限を外部 ID に付与する GCP のコンソールで Workload Identity プールのページより事前準備で作成した Workload Identity プールを見つけて開きます。 Workload Identity プールを開いたところ 画面上部の アクセスを許可 をクリックし、開いたナビゲーションに従って必要な項目を入力していきます。 アクセス許可を設定 サービスアカウントには事前準備で作成したサービスアカウントを選択します。また、必要に応じて AWS ロールでのフィルタ条件を設定します。 保存 をクリックすると次のような画面がポップアップします。 構成ファイルをダウンロード プロバイダを選択し 構成をダウンロード をクリックすると構成ファイルがダウンロードされます。このファイルの中身は以下のような JSON になっています。 { " type ": " external_account ", " audience ": " //iam.googleapis.com/projects/************/locations/global/workloadIdentityPools/sample/providers/sample-aws ", " subject_token_type ": " urn:ietf:params:aws:token-type:aws4_request ", " service_account_impersonation_url ": " https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/workloadidsample@**************.iam.gserviceaccount.com:generateAccessToken ", " token_url ": " https://sts.googleapis.com/v1/token ", " credential_source ": { " environment_id ": " aws1 ", " region_url ": " http://169.254.169.254/latest/meta-data/placement/availability-zone ", " url ": " http://169.254.169.254/latest/meta-data/iam/security-credentials ", " regional_cred_verification_url ": " https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15 " } } サービスアカウントキーのような認証情報が含まれていないことがご確認いただけると思います。このファイルをサービス側に連携していただき、ユーザー環境での準備は完了です。 サービスワークロードでの処理 サービス側の AWS Lambda では次のようなコードを実行しています。 package main import ( "context" "gocloud.dev/blob" "gocloud.dev/blob/gcsblob" "gocloud.dev/gcp" "golang.org/x/oauth2/google" ) func WriteGCS(ctx context.Context, cred, bucketName, objectName string , content [] byte ) error { // 一時トークンを発行して GCP クライアントを生成 creds, err := google.CredentialsFromJSON(ctx, [] byte (cred), "https://www.googleapis.com/auth/devstorage.read_write" ) if err != nil { return err } client, err := gcp.NewHTTPClient(gcp.DefaultTransport(), gcp.CredentialsTokenSource(creds)) if err != nil { return err } // 以下は通常通り Bucket オブジェクトを生成して書き込み処理を実行 bucket, err = gcsblob.OpenBucket(ctx, client, bucketName, nil ) if err != nil { return err } if err := bucket.WriteAll(ctx, key, bt, nil ); err != nil { return err } return nil } 前提として Go CDK を利用しています。このモジュールはクラウドサービスへのアクセスを抽象化してくれているので、今回のように複数のクラウドサービスを跨いだ処理が必要な際にとても便利です。 WriteGCS 関数の引数には cred : ユーザー環境で取得していただいた構成ファイルの JSON 文字列 bucketName : 対象のバケット名 objectName : 対象のオブジェクト名 content : 対象のオブジェクトのコンテンツ がそれぞれ渡される想定です。 creds, err := google.CredentialsFromJSON(ctx, [] byte (cred), "https://www.googleapis.com/auth/devstorage.read_write" ) ここの3番目の引数はスコープを指定するのですが Google API の OAuth 2.0 スコープ というドキュメントがあるのでここから必要なスコープを指定します。ドキュメント内でも記載がありますが、できるだけ機密性の低いスコープを使用することが推奨されています。今回は Cloud Storage への読み込み/書き込みを可能にしたいので https://www.googleapis.com/auth/devstorage.read_write を指定しています。 以降、クライアントを生成してバケットオブジェクトの読み書きメソッドを呼ぶ流れはサービスアカウントキーを使った場合と同じです。実装時に認証方式をそれほど意識せずに済むようになっています。 まとめ Workload Identity 連携を利用することで、セキュリティリスクを最小限に抑えたシステム構成が実現できました。クラウドを活用したマイクロサービス化が進むとサービス間認証は課題となってくるので、今回のような仕組みを活用できる機会が今後増えてきそうです。
コーポレート・マーケティング室、デザインチームの高森です。アプトポッドのデザインチームでは、これまで Sketch と Zeplin を利用してデザインと開発側への共有を行ってきたのですが、今後デザインワークフローの改善やプロトタイプの共有しやすさを考慮し、Sketchから Figma へ移行することとしました。 Figmaについては紹介されている記事がたくさんあり、さっと使ってみた感じも使いやすかったため、操作にそこまで不安はなかったのですが、デザインツール変更にあたり確認したポイントを紹介します。SketchからFigmaへの移行を検討されてる方の参考になればと思います。 Figma移行時に確認したポイント ローカルフォントが問題なく使えるか ネットワークに繋がってない時も使えるか アカウント発行無しでデザインを共有できるか パスワード付きURLでデザインを共有できるか Figmaを選択した理由 プロトタイプが作りやすい&共有しやすい 学習コストが低い デザインファイルが管理しやすい 開発者へ共有しやすい コストパフォーマンスが良くなる ツールの安定性が高い 動きが軽い 導入してから気づいた課題 ライブラリは作り直しが必要 ライブラリ更新によるデザインの自動更新 おわりに Figma移行時に確認したポイント FigmaはブラウザベースのためPC環境に依存せずデザイナー以外でも使える点がメリットですが、デザイナー以外も使うからこその懸念点としてまず以下を確認しました。 ローカルフォントが問題なく使えるか aptpodの製品はAxisフォントを使用しているため、デザイナー以外のパソコンでもちゃんと表示されるのかを懸念しておりましたが、閲覧権限で共有すればフォントは問題なく表示されました。編集権限で共有した場合も表示はされますが、ツールバーにアラートが表示されフォントを置き換えまたはインストールするまでテキスト編集はできません。 ツールバーにアラートが表示される ネットワークに繋がってない時も使えるか オフライン状態でファイルを開くことはできませんが、作業途中にオフラインになった場合は問題なく作業を続けられました。変更内容は再度オンライン環境になった時に更新されます。移動中など万が一ネットワーク環境が悪い中で作業が必要になった場合でも使えそうです。 アカウント発行無しでデザインを共有できるか 閲覧権限であれば無料かつアカウント発行無しで共有できます。 デザインの編集やコード情報が不要な社内のメンバーやクライアントにデザインを共有する際に、URLのみで共有できるためとても便利です(Sketchではアカウント発行が必要でした) パスワード付きURLでデザインを共有できるか 共有時のURLにはパスワードが設定できるため、社外のメンバーに共有する際にも問題ありません。(パスワード付きのURLは有料アカウントで利用可能です。無料アカウントではメールアドレスで招待、パスワード無しのURL共有が可能です) Figmaを選択した理由 今回、初めからFigmaへの移行を検討していたわけではなく、デザインデータの運用ルールを定めたりデザインガイドラインを作成する取り組みの一環としてツール検討を進めていました。そのため本格的なプロトタイプツールからデザインドキュメントツールなどを検討し、結果的に以下の観点でFigmaを導入することとしました。(デザインドキュメントツールの検討経緯は こちら でも紹介しています) プロトタイプが作りやすい&共有しやすい Sketchのプロトタイプ機能はページ遷移をつける程度しかできないため今ひとつ利用機会がなかったのですが、Figmaではページ遷移、オーバーレイ表示、ボタン状態の設定など、必要最低限のプロトタイプ機能が簡単に作れる様になっています。 アカウント発行不要で共有できるため社外のクライアントへも共有しやすくプロトタイプ作成⇄レビューのやり取りがしやすくなりそうです。 学習コストが低い FigmaとSketchでは、Symbol(Figmaではコンポーネント)へのアクセス、ArtboardがFrame/Groupになっているなど大きな違いはいくつかあるのですが、基本的なショートカットや機能は共通で、これまでSketchを使っていたメンバーなら習得しやすいと感じています。 Sketchファイルがインポートでき、こちらもレイアウトのずれやシェイプの崩れはいくつかありますが、事前にSketch側でファイルを調整すれば問題なく調整できるレベルなので、これまでのSketchファイルも活用できそうです。 デザインファイルが管理しやすい Sketchには無かったVariant機能によってパーツの状態やタイプをグルーピングできるので、ライブラリの管理がとても便利になりそうです。 開発者へ共有しやすい ブラウザベース且つコーディング情報を取得できるため(要アカウント発行)、これまでZeplinを利用していた開発者への共有をFigmaで完結できるのではないかと考えています。デザインファイル上にコメントも記入できるため、やりとりもしやすそうです。 Inspectタブからコーディング情報取得可能 デザインファイル上に直接コメント可能 コストパフォーマンスが良くなる SketchとZeplinで進めきたワークフローをFigmaで完結できる様にした場合、これまでよりもコストが抑えられそうです。 開発側への共有に必要なデザインの閲覧とコーディング情報取得は無料アカウントの範囲で可能なため、デザイナーのみの有料アカウントで十分になります。 ※aptpodではProfessionalプランを利用することにしましたが、社内にデザインチームが複数あり権限を分ける必要があるといった場合はOrganizationプランが良さそうです。Organizationプランの内容に関しては こちら の記事を参照してます。 ツールの安定性が高い Figmaは現在UIデザインツールとしてメジャーなものとなっていて、 2021 Design Tools Survey によると、UIデザインツールとしてメインでFigmaを使っている人は最も多い割合になっています。サポートやアップデートの観点からユーザー数が多いという点はとても心強いです。 動きが軽い Figmaは動きが軽いと聞いていましたが、実際に使ってみても軽く感じています。Sketchではファイルを開くのに時間がかかったりアプリが突然落ちることも頻発していたため、ストレス無く作業ができる様になりました。今後プロジェクトやページ数が増えていっても軽さが保たれるかは要経過観察ではあります。 導入してから気づいた課題 ライブラリは作り直しが必要 FigmaにもSketch同様ライブラリ機能がありますが、ライブラリは有料版の機能だったため、導入するまでどのような使い勝手か確認できませんでした。 実際に有料版を契約しSketchからライブラリデータを移行したところ、ライブラリから参照しているリンクは切れてしまうため、再度Figma上でライブラリからコンポーネントを割り当てる必要がありました。こちらについてはコンポーネントの階層の扱い方がSketchと異なるため、ライブラリ自体作り直す方が早そうですが、Variant機能があるため作り直しはそれほど手間はかからずできると思います。 ライブラリ更新によるデザインの自動更新 ライブラリを更新することで、古いデザインを残しておけなくなることに気づきました。 これまではZeplin上に切り離して共有していたため、ライブラリを更新した場合もZeplin上のデザインには影響がなかったのですが、デザイナーが作業するファイルと開発者が参照するファイルが同一の場合、開発版とリリース版とデザイン途中のファイル、それぞれの管理をしっかりしておく必要があります。同じライブラリを使っている場合、参照しているコンポーネントが一斉更新されて問題がないかなど、今後ファイルの管理方法に工夫が必要そうです。 おわりに まだまだFigmaを導入し始めたばかりでデータの移行も完了しておらず、実際に導入することになってから気づいた細かい不明点も出始めてきておりますが、現状のところ軽さというのが大きな助けとなっており大きなストレスなく作業できています。使い方については検索すればたくさん出てくる点もとても心強いです。また、最近日本語にも対応されたため、これから導入する際により使いやすくなりそうです。( Figma日本語対応のお知らせ ) デザイン作業だけでなく、ワークフローをスムーズに運営できることを意識して、Figmaをこれから開拓していきたいと思います。最後まで読んでいただきありがとうございました。
こんにちは。intdash グループ フロントエンドエンジニアの佐藤です。 Next.js を使った管理画面を作成するプロジェクトを担当する機会がありました。 管理画面は「 頻繁にデータが更新されることがない 」、「 同時アクセスはあまり起きない 」という前提の元に作成することが多いと考えています。 なのでサーバー、表示ともにパフォーマンス的に無駄なリクエストを投げたくはありません。 それを解決するため、プロジェクトに React Query を導入しました。 今回はその導入事例をご紹介したいと思います。 SSRを使用してデータを表示する データを更新する まとめ SSRを使用してデータを表示する SSR については下記のリンクが参考になります。 nextjs.org まずサーバーサイドはgetServerSideProps でデータを取得します。 // index.tsx const USER_KEY = "USER_KEY" ; const fetchUser = async ( baseUrl: string ) : Promise < { name: string } > => { const res = await fetch ( ` ${ baseUrl } /api/user` ); return await res.json (); } ; export const getServerSideProps: GetServerSideProps < ServerSideProps > = async ( { req , } ) => { const queryClient = new QueryClient (); const protocol = req.headers [ "x-forwarded-proto" ] || "http" ; const baseUrl = req ? ` ${ protocol } :// ${ req.headers.host } ` : "" ; await queryClient.prefetchQuery ( [ USER_KEY ] , () => fetchUser ( baseUrl )); return { props: { baseUrl , dehydratedState: dehydrate ( queryClient ), } , } ; } ; 次に fetchUser でPrefetchしたデータを USER_KEY のQuery に紐づけます。 dehydrate でクエリキャッシュをDehydrate し、dehydratedState を介してページにProps として渡します。 queryClient.prefetchQuery で特徴的なのはその返り値です。 公式ドキュメント にある通り Promise<void> を返します。エラーもスローされません。 エラーのハンドリングや取得したデータをサーバーサイドで使用する場合は queryClient.fetchQuery を使います。 (参考: https://github.com/TanStack/query/discussions/1494#discussioncomment-226271 ) クライアントサイドでは useQuery を使って実装します。 const Page: React.FC < ServerSideProps > = () => { const { data } = useQuery ( [ USER_KEY ] , () => fetchUser (), { staleTime: Infinity , } ); return ( < div > < p > { data.name } < /p > < /div > ); } ; useQuery のQuery Key はサーバーサイドと同様に USER_KEY を指定します。 オプションには staleTime: Infinity を追加しました。 このオプションによりキャッシュは常に「新鮮 (新しい)」と見なされ、バックグラウンドでの再取得が発生しません。 ブラウザでのReact Query の動作の様子 StaleTime やCacheTime については下記のブログがわかりやすかったです。 tkdodo.eu データを更新する 「データを表示する」でオプションに staleTime: Infinity を追加しました。 これによりデータの再取得は「キャッシュを無効化 (= 古くなったとみなす)」させるか「キャッシュをリセット」する必要があります。 「キャッシュの無効化」するには queryClient.invalidateQueries を使います。 const updateName = React.useCallback (() => { mutate ( name , { onSuccess: () => { queryClient.invalidateQueries ( [ USER_KEY ] ); } , } ); } , [ mutate , name , queryClient ] ); updateName が実行される際に、 USER_KEY に紐づくキャッシュを無効化しました。 同じような形で「キャッシュをリセット」するには queryClient.resetQueries を使用します。 この2つはメソッドを実行したときのデータの残り方に違いがあります。 queryClient.invalidateQueries は無効化されたキャッシュが再取得完了まで残ります。 一方、 queryClient.resetQueries は再取得前にキャッシュが初期状態にリセットされるので「なにも取得していない状態」になってから再取得をします。 どちらが良いかはワークフローによると思いますが、管理画面は「更新しようとしているデータ」をわかりやすくするために queryClient.invalidateQueries が良さそうです。 まとめ React Query はAPI の状態を返してくれるだけではなく、強力なのはそのキャッシュ機構にあると考えます。 このキャッシュをうまく使いこなせれば、リクエストを減らすことができ、よりよいユーザー体験につながるアプリケーションにすることができます。
主にネイティブアプリケーションの開発を担当している上野です。 先日、東京ビッグサイトにて開催された第5回5G通信技術展に出展し、 リアルタイムデジタルツイン及び産業分野別ソリューションなどのいくつかのデモンストレーションを行いました。 www.aptpod.co.jp この記事では展示したデモの1つである "リアルタイム"デジタルツイン について、開発を担当していた私が解説をしたいと思います。 そもそもデジタルツイン(DigitalTwin)とは "リアルタイム"デジタルツインとは デモンストレーション構成 実際にアプリを開発してみて Unityを選択した理由 位置測位精度の課題 Unityによる業務アプリケーション開発の難しさ 今後について そもそもデジタルツイン(DigitalTwin)とは デジタルツイン(DigitalTwin)とは、現実の世界から収集した様々なデータを、まるで双子であるかのように、コンピュータ上で再現する技術のことです。コンピュータ上では、収集した膨大なデータを元に、限りなく現実に近い物理的なシミュレーションが可能となり、自社製品の製造工程やサービスの在り方をより改善するうえで有効な手段となります。例えば製造ラインの一部を変更する場合など、事前にデジタルツイン上でテスト運営することで、開発期間やコストの削減が見込めます。 引用元: デジタルツインとは?意味・定義 | ITトレンド用語 | ドコモビジネス | NTTコミュニケーションズ "リアルタイム"デジタルツインとは 前項で デジタルツインは膨大なデータを元に とある様に実際にデジタルツインで取り扱うデータは大量のデータを扱うことが多く、更新の頻度も数十秒以上かかるものが多いなど高頻度に情報を取り込むのは難しい様です。 弊社では intdash という高速でかつ大容量のデータ伝送を行えるプラットフォームと Visual M2M Data Visualizer のような伝送されたデータを数十~数百ミリ秒の遅延で可視化する製品を持つなど リアルタイム に取り扱うノウハウを持っています。 このデモは、アプトポッドの技術と最近注目されつつあるデジタルツインを組み合わせてなにか出来ないかを調査・検討した プロトタイピングプロジェクトの成果 になります。 ※ 今回展示した デジタルツインデモ は、すぐにご提供できる商品ではありません。研究開発成果をご紹介するためのものとお考えください。 デモンストレーション構成 デモンストレーション構成 今回デモアプリとして、バーチャル空間上に現実世界の倉庫を再現するデモアプリを開発しました。また現実世界としては、実際の倉庫を用意するまで準備コストをかけられなかったため、代替として物理シミュレーターである Gazebo を使用しました。 youtu.be デモアプリとGazeboシミュレーターには Turtlebot3 を設置しています。 今回のデモでは リアル環境の代替としてクラウド上に設置したGazeboシミュレーターの情報をintdashを用いて伝送し続けています。 展示会場には一般的なゲームパッド *1 が接続された Raspberry Pi を設置し、Raspberry Piに組み込まれた弊社製品である intdash Edge Agent を用いてintdashのプラットフォームを利用してコントロール情報をGazeboシミュレーターに伝送することでTurtleBotの遠隔制御が可能な状態にしています。 伝送され続けているリアル環境データは数十から数百ミリ秒ほどの遅延で開発したデモアプリに表示されています。 バーチャル空間上では、倉庫内の固定物(棚など)は3Dモデルとして描画しています。一方でダンボールなどの可動物は、事前にモデリングできないため、本来であれば3Dモデルとして描画することはできませんが、今回のデモではわかりやすさのためにワイヤフレームの形式で描画しています。 実際の利用シーンでは、ダンボールなどの可動物は Turtlebot3 に搭載した LiDAR などによって検出する想定となるため、LiDARから取得した点群をバーチャル空間にオーバーレイして表示しています。 という内容となっています。 以上、デモンストレーションの内容に関しては 動画 にて詳しく解説されておりますので是非ご覧ください。 youtu.be 実際にアプリを開発してみて ここからは開発者目線でのお話をしたいと思います。 今回展示したデモは弊社では初の試みで Unity を用いて開発しております。 近年デジタルツインと始めとする3次元データや3Dモデルの可視化ツールの需要が高まってきており、その開発の主軸としてUnityや UnrealEngine などの一般的にはゲーム用エンジンと呼ばれる3D開発プラットフォームが注目されています。 Unityを選択した理由 Unity を選択した理由は、インターネット上に存在する情報、特に日本語情報の多さです。さらにUnityの主な開発言語はC#で、C#はゲーム以外にも利用される言語であるため、今回のようなゲームではない産業アプリケーションの開発に関する情報も多いと考えました。また、C#での開発で不足があればC++を使用することも可能です。一般的なゲーム開発から少し外れる今回の開発プロジェクトでは、こういった情報量の多さや開発の自由度の高さがプラスに働くと考えました。 位置測位精度の課題 実は今回のデモでは、ロボットの位置はGazeboシミュレーターが出力する値を直接取得して使用しています。実際の現実世界でこのデモを使用する場合には、例えば SLAM(Simultaneous Localization and Mapping) のような位置測位技術を使用して、様々なセンサーから算出した推定位置を使用しなければなりません。今回のデモではTurtleBotに搭載されたLiDARによりダンボールなどの障害物を検出してバーチャル空間上にマッピングしていますが、このマッピングが極めて正確に行えるのは、完全な位置情報が推定されている状態だったためです。 実際の現実世界で今回のデモのように正確に障害物の位置をマッピングするには、相当精度のよい位置測位技術が必要となりますが、現在屋内における位置測位技術は屋外におけるGPSのようなスタンダードが確立していない状況です。もし屋内位置測位技術として、汎用的かつ精度の高い技術をお持ちの場合は、ぜひお声がけください。 また、以前私が執筆した記事 *2 ではiPhoneを用いて地形をマッピングする様なデモを行いましたがあれらはAppleが提供する高度な自己位置推定機能と位置測位技術をユーザーへ提供しているからこそ実現しています。 取得できた点群データをサーバへ送信し、別PCの3D仮想空間上に表示してみた #iPhone12Pro #LiDAR #ARKit #PointCloud #Demo pic.twitter.com/ioxaAQtIfa — aptueno (@aptueno) 2020年12月16日 デジタルツインにおいてこの 自己位置推定の課題 は大きいと言えます。 Unityによる業務アプリケーション開発の難しさ 今回のデモアプリケーションは、業務アプリケーションとして使用できるように、各種ウィンドウやマウスクリックで操作する画面要素(テキストボックスやドロップダウンリストなど)もUnityで表現することにチャレンジしてみました。iPhoneアプリやWebアプリケーション開発であれば標準コンポーネントとして簡単に導入できる画面要素でも、ゲームであまり使用されないものはUnityには存在しなかったため、スクラッチから開発する必要がありました。 また、Unityでは2Dの画面をただ描画するだけでも、裏側には必ず3Dの要素を保つ必要があり、想定以上にコンピューティングリソースが必要になるという問題もありました。現在ゲームエンジンの産業利用は進んできていますが、業務アプリケーションのような単純な画面構成を開発する用途には、まだまだ最適化されていない感じることが多くありました。 今後について 弊社のプロダクトにUnityを組み合わせることで実現される、ユースケースの広がりを想像いただけましたでしょうか。 今回のデモで得られた知見は、今後お客様からご依頼いただく開発プロジェクトにも活かしていきたいと思っております。 デジタルツインシステムを作ってみたいが、何から手を付けてよいか分からない 既存のデジタルツインシステムにもっとリアルタイム性をもたせたい AGVや自動運転車両のリアルタイム監視を行いたい 監視だけでなく遠隔操作も行いたい データ伝送の必要なアプリケーションを作りたいが、通信プロトコルに詳しくない Unity を使って業務アプリケーションを作ってみたい 大量データを扱えるハイパフォーマンスな可視化アプリケーションを開発したい などなど、アプトポッドではIoTに関する豊富な実績と高い技術力で、お客様の課題解決にご協力いたしますのでお気軽にお問合せください。 www.aptpod.co.jp *1 : ※使用したゲームパッドは こちら *2 : ここから始まるお手軽地形計測 iPhoneへLiDARスキャナ搭載【ARKit】 - aptpod Tech Blog
開発本部 SRE グループの金澤です。 今回は GitLab にアルファ版としてサポートされた OIDC(OpenID Connect) を使用して、よりセキュアな環境で GitLab CI/CD を実施するようにした内容をご紹介します。 現状と課題 CI/CD と OpenID Connect 設定 クラウドインフラでOpenIDプロバイダーを登録する 登録した OpenID プロバイダー用の IAM ロールを作成する GitLab プロジェクトへの変数定義 CI/CD を動作させて確認する 結果 注意点 まとめ 現状と課題 弊社製品の intdash のサーバサイドソフトウェアである intdash Server は、基本的にクラウドインフラを使用して構築しており、Terraform や Ansible による構成管理を行っています。コード管理には GitLab を利用しており、GitLab はセルフマネージドで運用しています。 GitLab は契約の種類(SaaS・セルフマネージド)を問わず CI/CD 機能を使用することができます。インフラのソースコードについても本機能を利用してコードの正常性を担保するようにしています。コードの内容と実装が相違ないことの確認や、必要な定期処理の実施が主な目的です。 インフラにおける CI/CD 利用時はその特性上、クラウドインフラへのリソースアクセスを行う機会が多く、これまでは各クラウドインフラのID・アクセス管理(IdAM/IAM)サービスから認証情報を払い出し使用していたのですが、払い出した認証情報のローテーション対応や万が一の漏洩に対するリスク管理が必要となり、セキュリティの管理的にはあまり望ましいオペレーションではありませんでした。 CI/CD と OpenID Connect OIDC(OpenID Connect)は、OAuth 2.0 をベースとする認証プロトコルです。 GitHub の CI/CD 機能である GitHub Action は昨年11月末に 対応アナウンス があり話題になりましたが、GitLab も今年1月にリリースされた GitLab 14.7 にて アルファ版として機能対応が発表されました 。 本機能を使用することによって、ID・アクセス管理サービスから認証情報を払い出す必要はなくなり、一時的な認証情報によってクラウドインフラのリソースを操作することが可能となりました。払い出されるのは一時的な認証情報となるので、前述したローテーション管理やリスク管理を軽減することが期待できます。GitLab では このような認証ワークフロー で動作します。 設定 それでは実際に設定していきます。今回は「あらかじめ認証情報を登録せず GitLab CI からクラウドインフラリソース(Amazon S3)を参照できること」を確認します。 設定する内容については GitLab 公式ドキュメント で紹介されています。この内容を踏まえながら、Terraform コードベースで設定していきます。アプトポッドではクラウドインフラとして主に AWS を利用していますので、登録するクラウドインフラを AWS としています。 クラウドインフラでOpenIDプロバイダーを登録する クラウドインフラに OpenID プロバイダーを登録します。 aws_iam_openid_connect_provider リソースを使用して OpenID プロバイダーを設定します。 url: GitLab インスタンス URL ex) " https://gitlab.example.net " client_id_list: GitLab インスタンス URL ex) " https://gitlab.example.net " thumbprint_list: こちら を参照して算出する。 resource " aws_iam_openid_connect_provider " " oidc-sample " { url = " https://gitlab.example.net " client_id_list = [ " https://gitlab.example.net ", ] thumbprint_list = [ " 算出した値 ", ] } 手動で登録する場合 thumbprint_list の値は AWS コンソール上に存在する「サムプリント取得」というボタンを押下することで取得することが出来ますが、コードで表現する場合は事前に取得する必要があります。事前取得ではなく自動化したい場合は、 tls_certificate というデータリソースを使用して実現することができます。(今回は実施していません。) 登録した OpenID プロバイダー用の IAM ロールを作成する 登録した OpenID プロバイダーを信頼されたエンティティに設定するかたちで、IAM ロールを作成します。 statement.condition.variable: GitLab インスタンス URL および JWT フィールド ex) "gitlab.example.com:sub" statement.condition.values: 対象とする GitLab プロジェクトおよびブランチ ex) "project_path:mygroup/myproject:ref_type:branch:ref:main" 下記の例の statement.condition.values ではワイルドカードを指定することもできます。ワイルドカードを指定することによって1つのプロジェクトにおける複数のブランチを対象とすることができます。その場合、 statement.condition.test の値を StringLike としてください。指定の方法は 公式ドキュメント にも記載がありますのでご参照ください。 resource " aws_iam_role " " oidc-sample " { name = " role_oidc_sample " assume_role_policy = data.aws_iam_policy_document.oidc - sample - principal.json } data " aws_iam_policy_document " " oidc-sample-principal " { statement { actions = [ " sts:AssumeRoleWithWebIdentity " ] principals { type = " Federated " identifiers = [ aws_iam_openid_connect_provider.oidc - sample.arn ] } effect = " Allow " condition { test = " StringEquals " variable = " gitlab.example.com:sub " values = [ " project_path:mygroup/myproject:ref_type:branch:ref:main " ] } } } resource " aws_iam_policy " " oidc-sample " { name = " for-oidc " path = " / " policy = data.aws_iam_policy_document.oidc - sample - policy.json } data " aws_iam_policy_document " " oidc-sample-policy " { statement { actions = [ " s3:* ", ] resources = [ " * " ] effect = " Allow " } } resource " aws_iam_role_policy_attachment " " oidc-sample " { role = aws_iam_role.oidc - sample.name policy_arn = aws_iam_policy.oidc - sample.arn } GitLab プロジェクトへの変数定義 作成した IAM ロールの ARN を GitLab CI の変数定義に登録します。今回登録する変数は IAM ロールの ARN のみとしています。 GitLab CI/CD 変数定義 CI/CD を動作させて確認する 実際に CI/CD を動作させて確認してみます。 .gitlab-ci.yaml は以下のように記載しています。表記中の CI_JOB_JWT_V2 はOIDCサポートと同時に導入されたJWT(JSON Web Token)です。こちらを利用してクラウドインフラとJWTトークンベースの接続を行っています。CI スクリプトの中で echo を行い存在の確認をしようとしています。 .gitlab-ci.yaml image : name : amazon/aws-cli:latest entrypoint : - '/usr/bin/env' assume role : script : - echo ${CI_JOB_JWT_V2} - > STS=($(aws sts assume-role-with-web-identity --role-arn ${ROLE_ARN} --role-session-name "GitLabRunner-${CI_PROJECT_ID}-${CI_PIPELINE_ID}" --web-identity-token $CI_JOB_JWT_V2 --duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text)) - export AWS_ACCESS_KEY_ID="${STS[ 0 ]}" - export AWS_SECRET_ACCESS_KEY="${STS[ 1 ]}" - export AWS_SESSION_TOKEN="${STS[ 2 ]}" - aws s3 ls 結果 GitLab CI の結果を記載します。GitLab 上にはアクセスキーなどの認証情報を登録していませんでしたが、 aws s3 ls で現在の S3 バケットの一覧情報が表示されました。 CI_JOB_JWT_V2 については、 [MASKED] といった形で隠蔽されることが分かりました。このことから aws sts assume-role-with-web-identity で一時的な認証情報が取得できていそうです。 ( 中略 ) $ echo ${CI_JOB_JWT_V2} [ MASKED ] $ STS = ( $ ( aws sts assume-role-with-web-identity --role-arn ${ROLE_ARN} --role-session-name " GitLabRunner- ${CI_PROJECT_ID} - ${CI_PIPELINE_ID} " --web-identity-token $CI_JOB_JWT_V2 --duration-seconds 3600 --query ' Credentials.[AccessKeyId,SecretAccessKey,SessionToken] ' --output text )) $ export AWS_ACCESS_KEY_ID= " ${STS[ 0 ]} " $ export AWS_SECRET_ACCESS_KEY= " ${STS[ 1 ]} " $ export AWS_SESSION_TOKEN= " ${STS[ 2 ]} " $ aws s3 ls 2020-06-23 06:08:59 analytics-blobs-dev-log-personal 2020-06-23 06:20:23 analytics-blobs-dev-personal : 注意点 冒頭でも記載しました通り、(2022/07/07 時点で) 本機能はアルファ版であり 本番環境での使用はまだ適していない ことにご注意ください。 まとめ 今回は、GitLab でサポートされた OIDC(OpenID Connect) 機能を利用して、よりセキュアな環境で GitLab CI/CD を実施するようにした内容をご紹介しました。 あらかじめ認証情報を払い出し GitLab シークレットに登録することなく、クラウドインフラのリソースを操作できることが確認出来ました。これにより GitLab CI/CD におけるセキュリティ面の向上はもちろん、CI/CD を利用したいが認証情報の取り扱いなどから生まれる心理的な障壁の一端を無くすことができると考えています。 本記事が少しでも参考になりましたら幸いです。
はじめに DX推進事業本部 クロスインダストリーグループの渡辺です。 ソリューションアーキテクトを担当しています。 「ソリューションアーキテクト」(以降SAと省略)という言葉が使われる機会は増えてきましたが、まだまだ馴染みの無い方は多いと思います。 今回は弊社のSAの仕事について紹介したいと思います。本記事で、SAという仕事に少しでも興味を持ってもらえると嬉しいです。 はじめに ソリューションアーキテクトとは アプトポッドのソリューションアーキテクト 例えばこんなこと ソフトウェア開発は初めてです こんなご相談も ソリューションのご提案もいたします スマートインフラメンテナンス ロボットフリート&遠隔制御 5G通信技術展でご覧頂けます おわりに ソリューションアーキテクトとは 自社のサービス・技術力を深く理解するとともにお客様の業務を理解して、お客様が課題解決を行うサポートをさせて頂く仕事です。 カッコ良く書けばこういう事になりますが、 お客様の「やりたいこと」を聴いて一緒に実現する。 お客様の「課題・問題」を聴いて一緒に解決する。 という仕事です。 アプトポッドのソリューションアーキテクト アプトポッドは、自社製品である「 IoTプラットフォーム intdash 」を活用して、産業IoTに取り組まれるお客様のデータ伝送、データ収集、データ活用に関する課題を解決するためのソリューションを提供する会社です。 intdash の特長としては以下があげられます。 サーバを介しても低遅延であり、高いリアルタイム性でデータ伝送が行える 不安定な通信環境化でも、伝送時にロストしたデータの完全回収ができる エッジサイドでデータに時刻を付与することで、多種多様なデータを時刻同期させてデータ管理が行える リアルタイムのデータ活用だけでなく、サーバに保存された過去データも容易に分析、学習などに利活用できる 上記の特長を活かし、intdashは、自動車、重機・建機・農機、工場機械、ロボットなど様々な分野のお客様にご活用頂いています。 と、intdashの説明するのは簡単なのですが、これだけではお客様が 「自社サービスにintdashを利用したら何が出来るか?」 を明確にイメージして頂くには至りません。 そこには、ギャップが在る、と言えます。 このギャップを埋めるお手伝いをするのが、SAの役割です。 新しいサービスを実現したい。intdashを使えば出来そうなんだけど。 日常業務でコストを削減したい。intdashを使えば出来そうなんだけど。 とりあえずintdashって使えそうな気がする。 そんなお話をお聴きするところからSAの仕事が始まり、お客様が望むサービスや業務改善を実現するまでのお手伝いをさせて頂いています。 例えばこんなこと ソフトウェア開発は初めてです IoTに関わっていますと、専らハードウェア開発に専念してきた、というお客様とお仕事をさせて頂く機会があります。 「監視パネルをWEB上で実現したい」 とご相談を受けました。 やや特殊なハードウェアを扱っておられるお客さまでしたので 操作パネルがどういう動作をするのか なぜそういう動作をするのか とお客様にヒアリングしながら私たちは勉強をし、 お客様にも WEBアプリにはどういう特性があるのか どういう操作がWEB上では使いやすいのか の様なことをレクチャーして勉強して頂きながら、膝を突き合わせて議論を重ねて最適なWEBシステムを作り上げていく。 とこういったお手伝いをさせて頂きました。 こんなご相談も 「空はどこからが空なのかを知りたい」 こんなご相談を受けたこともありました。 ドローン開発に当たってintdashを導入して頂くことが決まっていたお客様からのご相談です。 空を飛ぶドローンでモバイル回線を使用する場合は特別な許可が必要です。 では許可を得なければいけない条件の 「空を飛ぶ」 とはどういうことなのか、10mを超えれば空なのか、それとも1cmでも飛んでいれば空なのか? そういう疑問から出てきたご相談でした。モバイル回線を用いて確実なデータ伝送を行えるのが当社サービスの強みでもあるので、当社にとっても大事なことです。 関係する法規を調査し、モバイル回線会社にも問い合わせをし、結果は 「1cmでも飛んでいれば空」 でした、なんだか腑に落ちたような、落ちないような、、、 ソリューションのご提案もいたします ソリューションアーキテクトはお客様からのご相談をお受けするだけではなく、お客様のニーズに対応したソリューションを企画立案しご提案いたします。 スマートインフラメンテナンス 工事現場情報など、インフラ事業者様が必要とする環境情報を、モビリティから取得する動画像を元にAIによるリアルタイム検知とGPSによる位置特定を行い、リアルタイムに自動検知、収集、可視化するソリューションです。 「 EDGEPLANT T1 」に接続されたUSBカメラを用いて映像を撮影しながら、デバイス上で動作させるAIモデルで画像を検出しています。 当社製デバイスの特徴を有効活用したソリューションです。 社会インフラに関わっているお客様にご提案させて頂いています。 【USBカメラで撮影しながら画像検出を行っている様子です】 youtu.be ロボットフリート&遠隔制御 5G 時代に本格化する自動化と遠隔化を見据えて、「intdash」はROS1およびROS2にも対応しています。こちらはその機能を活かして、ロボットや各種モビリティなど、機体の遠隔制御、機体データの遠隔モニタリングとデジタルツイン対応などを実現するソリューションです。 「 IoTプラットフォーム intdash 」は様々なデバイス同士を繋ぐことが出来ます。 その特徴を活かし、リアルとバーチャルリアリティを繋ぎました。 バーチャルリアリティ空間に存在するロボットを、リアル空間に存在するコントローラーを用いて操作しています。また、バーチャルリアリティ空間のロボットからクラウド空間にアップロードされるデータを「 Visual M2M Data Visualizer 」を用いて可視化しています。 AGVをはじめとする様々なロボット開発、自律/遠隔制御されるモビリティの開発に関わっているお客様にご提案させて頂くソリューションです。 【VRロボットをコントローラーで操作している様子です】 youtu.be 【ロボットの各種データを可視化している様子です】 youtu.be 5G通信技術展でご覧頂けます この度、 5G通信技術展 でこれらのソリューションの幾つかをデモンストレーションさせて頂くことになりました。 是非当社ブースまでお越しください。 www.aptpod.co.jp おわりに 本記事で、当社のサービス、またソリューションアーキテクトに少しでも興味を持ってもらえると嬉しいです。 そして 新しいサービスを実現したい。intdashを使えば出来そうなんだけど。 日常業務にコストを削減したい。intdashを使えば出来そうなんだけど。 とりあえずintdashって使えそうな気がする。 と思ったお客様はぜひお気軽に当社までお問い合わせください。 5G通信技術展 にもお越しいただけることを楽しみにお待ちしております。 www.aptpod.co.jp
はじめに プロフェッショナルサービス本部 SRE グループのkawamata です。 今回は at least once を保証するメッセージングシステムを構成可能なミドルウェアである、 NATS JetStream を試してみました。 弊社 intdash を構成する intdash Server でもメッセージングシステムが動作しています。 現状は、そのメッセージングシステムの一部に NATS Streaming を採用しています。 NATS Streaming は現在 deprecated となっており、 2023/06 にはサポートが終了 する予定です。本件への対応方針はプロダクト開発本部で検討中ですが、 SRE グループでも情報収集のため、 NATS Streaming の後継にあたる NATS JetStream をまずは試してみました。 はじめに NATS とは NATS NATS Streaming NATS JetStream NATS JetStream の特徴 設計思想 特徴 実際に試してみた デプロイ メッセージストリーミング Key Value ストア オブジェクトストア おわりに NATS とは まず、 NATS とは何か?について概要を説明します。 そもそも、メッセージングシステムに関して簡単に触れておきます。 メッセージングシステムはサービス間の非同期処理を実現します。 メッセージングシステムの利用者はPublisher, Subscriber と呼ばれます。 Publisher はメッセージングシステムにメッセージを送信し、Subscriber はメッセージングシステムからメッセージを受信します。 Publisher とSubscriber のメッセージングの間にシステムが入ることによって、両者は疎結合となり相互の処理の依存度が下がるため、非同期の処理が可能になります。 また、メッセージングシステムは主に送達されるメッセージの保証要件によって大別されます。 メッセージの送達保証には保証度の低い順に以下の種類があります。 at most once 1 回 は 送信 メッセージの送達は保証されない at least once 少なくとも 1 回送信 1 回 以上 のメッセージ送達を保証 メッセージの重複が有り得る exactly once 必ず 1 回送信 1 回 だけ のメッセージ送達を保証 メッセージの重複は発生しない NATS NATS は、 CNCF(Cloud Native Computing Foundation) のプロジェクトでホストされているシンプルかつ高性能なメッセージングシステムを構成可能なオープンソースのミドルウェアです。 NATS 単体では、 at most once なPub/Sub(Publish/Subscribe) システムを構成可能です。 at most once ですので、Subscriber は NATS サーバに接続していない間にPublisher から送られたメッセージを受信することが出来ません。 NATS Streaming NATS Streaming は NATS Server にStreaming module と永続化のためのstorage を組み合わせ、 at least once を保証するメッセージングシステムを構成可能にしたものです。 at least once が保証されますので、Subscriber(この場合Consumer) はPublish されたメッセージを文字通り必ず1 回は受信可能です。 NATS Streaming はシンプルでスケールさせやすい優れたメッセージングシステムですが、いくつかの問題点がありました。 例として、以下の課題があります。 at least once のためメッセージが重複する可能性がある 確認済みのメッセージをシステムから削除出来ない Consumer はメッセージをpull 出来ない Subscribe の制限が柔軟に出来ない クラスタのスケールアウトにおいて、スケーラビリティが低い NATS 2.0 のセキュリティモデルに対応していない NATS JetStream NATS JetStream は前述の課題を解決するべく、 NATS Streaming の後継として開発されたものです。 NATS Server とは別に開発されていましたが、 NATS 2.2.0のタイミングでGA(Generally Available) となり、現在は NATS Server に組み込まれています。 NATS JetStream は exactly once を保証するメッセージングシステムを構成することが可能です。 NATS Streaming と違いメッセージの重複は発生しないアーキテクチャになっています。 NATS JetStream の特徴 ここで、 NATS JetStream の設計思想や特徴をいくつか説明します。 設計思想 設計思想に関して、以下に 公式ドキュメント から抜粋します。 構成と操作が簡単で監視が容易 NATS 2.0 セキュリティモデルに準拠 スケールアウトによる高いスケーラビリティを有する 多様なユースケースをサポートする 自己回復性を有する メッセージペイロードに依存しない動作をする 特徴 NATS JetStream にはいくつか特徴があります。 以下に注目すべき特徴を説明します。 exactly once なメッセージングを保証 メッセージの再生ポリシーが多様 再生レートが選択可能 instant (Consumer が処理可能な最速で受信) original (Publisher が送信したレートで再生) メッセージの開始番号や時刻を選択可能 メッセージの確認応答の種類が豊富 メッセージストリーミング以外の機能も利用可能(JetStream は単なる永続層という位置づけ) Key Value Store 所謂、一般的なKVS として利用可能 KVS イベントをメッセージングとして受信可能 Object Store メッセージの容量制限を大きくすることでバイナリ等のデータオブジェクトをメッセージとしてストア可能 その他の特徴や制限事項は 公式ドキュメント をご確認ください。 実際に試してみた それでは、実際に NATS JetStream を試してみます。 公式の Helm charts がありますので、こちらを利用して 3 Node のNATS JetStream クラスタを Kubernetes 上で動作させます。 Kubernetes Operator 用に NATS Operator も存在していますが、こちらはNATS JetStream には対応していないため注意が必要です。 デプロイ 公式ドキュメント を参考に、helm を使用してデプロイしていきます。 repository の追加 $ helm repo add nats https://nats-io.github.io/k8s/helm/charts/ 設定ファイルの準備 以下の設定ファイル(YAML) を準備 NATS JetStream の有効化および、クラスタの設定定義 $ cat jetstream/app-config.yaml nats: image: nats:alpine jetstream: enabled: true memStorage: enabled: true size: 2Gi fileStorage: enabled: true size: 10Gi storageClassName: gp3 cluster: enabled: true replicas: 3 $ インストール $ helm install nats-jetstream nats/nats -n aptpod-demo -f jetstream/app-config.yaml NAME: nats-jetstream LAST DEPLOYED: Wed May 25 18:13:20 2022 NAMESPACE: aptpod-demo STATUS: deployed REVISION: 1 NOTES: You can find more information about running NATS on Kubernetes in the NATS documentation website: https://docs.nats.io/nats-on-kubernetes/nats-kubernetes NATS Box has been deployed into your cluster, you can now use the NATS tools within the container as follows: kubectl exec -n aptpod-demo -it deployment/nats-jetstream-box -- /bin/sh -l nats-box:~# nats-sub test & nats-box:~# nats-pub test hi nats-box:~# nc nats-jetstream 4222 Thanks for using NATS! $ 確認 pod が動作していれば成功 $ kubectl -n aptpod-demo get pods nats-jetstream-0 3/3 Running 0 73m nats-jetstream-1 3/3 Running 0 73m nats-jetstream-2 3/3 Running 0 73m nats-jetstream-box-65d7d89987-gbrg4 1/1 Running 0 73m $ メッセージストリーミング 公式ドキュメント を参考にメッセージストリーミングを試してみます。 nats-box にログイン $ kubectl exec -n aptpod-demo -it deployment/nats-jetstream-box -- /bin/sh ~ # stream の作成 intdash_test という名前で作成 ~ # nats stream add intdash_test ? Subjects intdash ? Storage file ? Replication 3 ? Retention Policy Limits ? Discard Policy Old ? Stream Messages Limit -1 ? Per Subject Messages Limit -1 ? Total Stream Size -1 ? Message TTL -1 ? Max Message Size -1 ? Duplicate tracking time window 2m0s ? Allow message Roll-ups No ? Allow message deletion Yes ? Allow purging subjects or the entire stream Yes Stream intdash_test was created Information for Stream intdash_test created 2022-05-25T09:46:03Z Configuration: Subjects: intdash Acknowledgements: true Retention: File - Limits Replicas: 3 Discard Policy: Old Duplicate Window: 2m0s Allows Msg Delete: true Allows Purge: true Allows Rollups: false Maximum Messages: unlimited Maximum Bytes: unlimited Maximum Age: unlimited Maximum Message Size: unlimited Maximum Consumers: unlimited Cluster Information: Name: nats Leader: nats-jetstream-0 Replica: nats-jetstream-1, current, seen 0.00s ago Replica: nats-jetstream-2, current, seen 0.00s ago State: Messages: 0 Bytes: 0 B FirstSeq: 0 LastSeq: 0 Active Consumers: 0 ~ # consumer の作成 以下2 つのconsumer を作成 intdash_consumer intdash_consumer2 ~ # nats consumer add ? Consumer name intdash_consumer ? Delivery target (empty for Pull Consumers) ? Start policy (all, new, last, subject, 1h, msg sequence) all ? Acknowledgement policy all ? Replay policy instant ? Filter Stream by subject (blank for all) intdash ? Maximum Allowed Deliveries -1 ? Maximum Acknowledgements Pending 0 ? Deliver headers only without bodies No ? Add a Retry Backoff Policy No ? Select a Stream intdash_test Information for Consumer intdash_test > intdash_consumer created 2022-05-25T09:52:54Z Configuration: Durable Name: intdash_consumer Pull Mode: true Filter Subject: intdash Deliver Policy: All Ack Policy: All Ack Wait: 30s Replay Policy: Instant Max Ack Pending: 1,000 Max Waiting Pulls: 512 Cluster Information: Name: nats Leader: nats-jetstream-1 Replica: nats-jetstream-0, current, not seen Replica: nats-jetstream-2, current, seen 0.00s ago State: Last Delivered Message: Consumer sequence: 0 Stream sequence: 0 Acknowledgment floor: Consumer sequence: 0 Stream sequence: 0 Outstanding Acks: 0 out of maximum 1,000 Redelivered Messages: 0 Unprocessed Messages: 10 Waiting Pulls: 0 of maximum 512 ~ # ~ # nats consumer add ? Consumer name intdash_consumer2 ? Delivery target (empty for Pull Consumers) ? Start policy (all, new, last, subject, 1h, msg sequence) all ? Acknowledgement policy all ? Replay policy original ? Filter Stream by subject (blank for all) intdash ? Maximum Allowed Deliveries -1 ? Maximum Acknowledgements Pending 0 ? Deliver headers only without bodies No ? Add a Retry Backoff Policy No ? Select a Stream intdash_test Information for Consumer intdash_test > intdash_consumer2 created 2022-05-25T09:53:42Z Configuration: Durable Name: intdash_consumer2 Pull Mode: true Filter Subject: intdash Deliver Policy: All Ack Policy: All Ack Wait: 30s Replay Policy: Original Max Ack Pending: 1,000 Max Waiting Pulls: 512 Cluster Information: Name: nats Leader: nats-jetstream-1 Replica: nats-jetstream-0, current, not seen Replica: nats-jetstream-2, current, seen 0.00s ago State: Last Delivered Message: Consumer sequence: 0 Stream sequence: 0 Acknowledgment floor: Consumer sequence: 0 Stream sequence: 0 Outstanding Acks: 0 out of maximum 1,000 Redelivered Messages: 0 Unprocessed Messages: 10 Waiting Pulls: 0 of maximum 512 ~ # メッセージのpublication subject は intdash 1 s 毎に10 メッセージを送信 ~ # nats pub intdash --count=10 --sleep 1s "publication #{{Count}} @ {{TimeStamp}}" 09:49:38 Published 37 bytes to "intdash" 09:49:39 Published 37 bytes to "intdash" 09:49:40 Published 37 bytes to "intdash" 09:49:41 Published 37 bytes to "intdash" 09:49:42 Published 37 bytes to "intdash" 09:49:43 Published 37 bytes to "intdash" 09:49:44 Published 37 bytes to "intdash" 09:49:45 Published 37 bytes to "intdash" 09:49:46 Published 37 bytes to "intdash" 09:49:47 Published 38 bytes to "intdash" ~ # intdash_consumer でメッセージ受信 メッセージ再生レート instant publishe 時のレートとは無関係に受信しているのがわかる ~ # nats consumer next intdash_test intdash_consumer --count 10 [09:55:04] subj: intdash / tries: 1 / cons seq: 1 / str seq: 1 / pending: 9 publication #1 @ 2022-05-25T09:49:37Z Acknowledged message [09:55:04] subj: intdash / tries: 1 / cons seq: 2 / str seq: 2 / pending: 8 publication #2 @ 2022-05-25T09:49:38Z Acknowledged message [09:55:04] subj: intdash / tries: 1 / cons seq: 3 / str seq: 3 / pending: 7 publication #3 @ 2022-05-25T09:49:39Z Acknowledged message [09:55:04] subj: intdash / tries: 1 / cons seq: 4 / str seq: 4 / pending: 6 publication #4 @ 2022-05-25T09:49:40Z Acknowledged message [09:55:04] subj: intdash / tries: 1 / cons seq: 5 / str seq: 5 / pending: 5 publication #5 @ 2022-05-25T09:49:41Z Acknowledged message [09:55:04] subj: intdash / tries: 1 / cons seq: 6 / str seq: 6 / pending: 4 publication #6 @ 2022-05-25T09:49:42Z Acknowledged message [09:55:04] subj: intdash / tries: 1 / cons seq: 7 / str seq: 7 / pending: 3 publication #7 @ 2022-05-25T09:49:43Z Acknowledged message [09:55:04] subj: intdash / tries: 1 / cons seq: 8 / str seq: 8 / pending: 2 publication #8 @ 2022-05-25T09:49:44Z Acknowledged message [09:55:04] subj: intdash / tries: 1 / cons seq: 9 / str seq: 9 / pending: 1 publication #9 @ 2022-05-25T09:49:45Z Acknowledged message [09:55:04] subj: intdash / tries: 1 / cons seq: 10 / str seq: 10 / pending: 0 publication #10 @ 2022-05-25T09:49:46Z Acknowledged message ~ # intdash_consumer2 でメッセージを5 ずつ受信 メッセージ再生レート original publishe 時のレートで受信しているのがわかる ~ # nats consumer next intdash_test intdash_consumer2 --count 5 [09:55:37] subj: intdash / tries: 1 / cons seq: 1 / str seq: 1 / pending: 9 publication #1 @ 2022-05-25T09:49:37Z Acknowledged message [09:55:38] subj: intdash / tries: 1 / cons seq: 2 / str seq: 2 / pending: 8 publication #2 @ 2022-05-25T09:49:38Z Acknowledged message [09:55:39] subj: intdash / tries: 1 / cons seq: 3 / str seq: 3 / pending: 7 publication #3 @ 2022-05-25T09:49:39Z Acknowledged message [09:55:40] subj: intdash / tries: 1 / cons seq: 4 / str seq: 4 / pending: 6 publication #4 @ 2022-05-25T09:49:40Z Acknowledged message [09:55:41] subj: intdash / tries: 1 / cons seq: 5 / str seq: 5 / pending: 5 publication #5 @ 2022-05-25T09:49:41Z Acknowledged message ~ # nats consumer next intdash_test intdash_consumer2 --count 5 [09:55:52] subj: intdash / tries: 1 / cons seq: 6 / str seq: 6 / pending: 4 publication #6 @ 2022-05-25T09:49:42Z Acknowledged message [09:55:53] subj: intdash / tries: 1 / cons seq: 7 / str seq: 7 / pending: 3 publication #7 @ 2022-05-25T09:49:43Z Acknowledged message [09:55:54] subj: intdash / tries: 1 / cons seq: 8 / str seq: 8 / pending: 2 publication #8 @ 2022-05-25T09:49:44Z Acknowledged message [09:55:55] subj: intdash / tries: 1 / cons seq: 9 / str seq: 9 / pending: 1 publication #9 @ 2022-05-25T09:49:45Z Acknowledged message [09:55:56] subj: intdash / tries: 1 / cons seq: 10 / str seq: 10 / pending: 0 publication #10 @ 2022-05-25T09:49:46Z Acknowledged message ~ # nats consumer next intdash_test intdash_consumer2 --count 5 nats: error: no message received: nats: timeout ~ # purge サブコマンドや rm サブコマンドでstream やメッセージの削除も可能になっています。 Key Value ストア 公式ドキュメント を参考にKey Value ストアも試してみます。 kvs の作成 intdash_kv という名前で作成 ~ # nats kv add intdash_kv Information for Key-Value Store Bucket intdash_kv created 2022-05-25T10:04:10Z Configuration: Bucket Name: intdash_kv History Kept: 1 Values Stored: 0 Backing Store Kind: JetStream Maximum Bucket Size: unlimited Maximum Value Size: unlimited JetStream Stream: KV_intdash_kv Storage: File Cluster Information: Name: nats Leader: nats-jetstream-1 ~ # key のput 2b126b37-f663-40e6-9ad6-2665b8ef2de3 というkey に intdash_X1 というvalue をストア ~ # nats kv put intdash_kv 2b126b37-f663-40e6-9ad6-2665b8ef2de3 intdash_X1 intdash_X1 ~ # key のget ~ # nats kv get intdash_kv 2b126b37-f663-40e6-9ad6-2665b8ef2de3 intdash_kv > 2b126b37-f663-40e6-9ad6-2665b8ef2de3 created @ 25 May 22 10:07 UTC intdash_X1 ~ # key のdelete ~ # nats kv del intdash_kv 2b126b37-f663-40e6-9ad6-2665b8ef2de3 ? Delete key intdash_kv > 2b126b37-f663-40e6-9ad6-2665b8ef2de3? Yes ~ # ~ # nats kv get intdash_kv 2b126b37-f663-40e6-9ad6-2665b8ef2de3 nats: error: nats: key not found, try --help ~ # key event の確認 ~ # nats kv watch intdash_kv [2022-05-25 10:08:09] DELETE intdash_kv > 2b126b37-f663-40e6-9ad6-2665b8ef2de3 ^C ~ # 特に問題なくKVS として動作しています。 オブジェクトストア 公式ドキュメント を参考にオブジェクトストアも試してみます。 オブジェクトストアの機能は現時点では Experimental Preview です。 オブジェクトバケットの作成 intdash_objbucket という名前で作成 ~ # nats object add intdash_objbucket Information for Object Store Bucket intdash_objbucket created 2022-05-25T10:12:48Z Configuration: Bucket Name: intdash_objbucket Replicas: 1 TTL: unlimited Sealed: false Size: 0 B Maximum Bucket Size: unlimited Backing Store Kind: JetStream JetStream Stream: OBJ_intdash_objbucket Cluster Information: Name: nats Leader: nats-jetstream-1 ~ # オブジェクトのput aptlogo-dark.png という弊社のロゴ画像ファイルをput ~ # nats object put intdash_objbucket aptlogo-dark.png Object information for intdash_objbucket > aptlogo-dark.png Size: 5.1 KiB Modification Time: 25 May 22 10:16 +0000 Chunks: 1 Digest: sha-256 43bcdf3b98e2e96bbd4f5bde5d32c29520f30fea7253cedba910334442ad ~ # nats object ls intdash_objbucket ╭───────────────────────────────────────────────────╮ │ Bucket Contents │ ├──────────────────┬─────────┬──────────────────────┤ │ Name │ Size │ Time │ ├──────────────────┼─────────┼──────────────────────┤ │ aptlogo-dark.png │ 5.1 KiB │ 2022-05-25T10:16:45Z │ ╰──────────────────┴─────────┴──────────────────────╯ ~ # ~ # nats object info intdash_objbucket Information for Object Store Bucket intdash_objbucket created 2022-05-25T10:12:48Z Configuration: Bucket Name: intdash_objbucket Replicas: 1 TTL: unlimited Sealed: false Size: 5.5 KiB Maximum Bucket Size: unlimited Backing Store Kind: JetStream JetStream Stream: OBJ_intdash_objbucket Cluster Information: Name: nats Leader: nats-jetstream-1 ~ # オブジェクトのget ~ # nats object get intdash_objbucket aptlogo-dark.png Wrote: 5.1 KiB to /root/aptlogo-dark.png in 0.00s ~ # ls aptlogo-dark.png ~ # オブジェクトのdelete ~ # nats object del intdash_objbucket aptlogo-dark.png ? Delete 5.1 KiB byte file intdash_objbucket > aptlogo-dark.png? Yes Removed intdash_objbucket > aptlogo-dark.png Information for Object Store Bucket intdash_objbucket created 2022-05-25T10:12:48Z Configuration: Bucket Name: intdash_objbucket Replicas: 1 TTL: unlimited Sealed: false Size: 267 B Maximum Bucket Size: unlimited Backing Store Kind: JetStream JetStream Stream: OBJ_intdash_objbucket Cluster Information: Name: nats Leader: nats-jetstream-1 ~ # nats object ls intdash_objbucket nats: error: nats: no objects found, try --help ~ # オブジェクトストアも機能上は問題なく動作しました。 ちなみに、pod のログを確認すると分かりますが、KVS やオブジェクトストアを作成した場合は以下のような接頭語をつけたstream が作成され、consumer は都度ランダムな文字列で作成される仕様になっています。 KVS KV_ 今回の例では KV_intdash_kv になる オブジェクトストア OBJ_ 今回の例では OBJ_intdash_objbucket になる おわりに 今回は exactly once を保証するメッセージングシステムを構成可能なミドルウェアである、 NATS JetStream を試してみました。 今の所、機能上は問題なく、 NATS Streaming が抱えていた種々の課題や制約が解消されていそうなため 弊社の次期メッセージングシステムの1つとして有望です。 今後、SRE グループでは以下に取り組みつつ、プロダクト開発本部と連携して他も鑑みミドルウェアの検討を続ける予定です。 詳細な仕様確認 特にstream 制限仕様の確認 詳細な機能試験 特に多様なAck 周り 非機能試験 特にスケーラビリティの確認 デプロイ方式の確立 既存の公式helm chart やOperator を参考にしながら自前でOperator を用意できると良いと考えています。
プロダクト開発本部EDGEPLANTグループのやべです。 皆さんGPS、使ってますか? 一昔前なら利用シーンも限られていましたが、今や生活の一部といってもいいレベルなので「使う」というイメージすらないかもしれません。 aptpod の計測ユースケースでも、GPSは不要、というものはあまりなく、自社で開発している EDGEPLANT T1 (以下T1、 Amazon で販売中 )にもGPS機能は搭載されています。 そんなGPSですが、スマホなど画面が一体となっていて確認しやすいものはともかく、機器に組込まれている場合はデータを確認するのにひと手間必要です。今回はその中でも、遠隔、つまり別のPCからデータを見る方法をいくつかご紹介します。 aptpod にお願いする 機材の設置 データの取得方法 u-center をネットワーク越しで利用する ser2netでシリアルデータをネットワーク上に流す ser2netのインストール ser2netの設定 u-center を使ってGNSSデータを見る u-centerのインストール u-centerの設定 おわりに aptpod にお願いする まず、aptpod がお客様に提供している方法を紹介したいと思います。先日、千葉県にある茂原ツインサーキットまで走りに行ったので、その際の映像を使いながら説明します。茂原ツインサーキットは手軽にサーキット走行できるとても楽しい場所で、年一くらいでお世話になっています。 www.mobara-tc.com 機材の設置 今回の記事とはあまり関係ないですが、こんな風に車両への取り付けを行いました。 せっかくだからといろいろセンサ類を取り付けています。ちなみに、機材を組んでいる銀色のラックは社内で工作されたものです。展示会向けにも作ることもあります。 データの取得方法 以下の図にあるような形で、 intdash Edge の機能を利用してGPSのデータをサーバーに送ります。冒頭で述べたようにGPSの利用は非常に多いため、デフォルトで機能は組込まれており、インストールするだけで利用可能です。 サーバーのデータは Visual M2M を利用してブラウザ上で確認することができます。 今回茂原サーキットにお邪魔した際は、T1のGPSモジュールに搭載されているIMU *1 の情報も表示させました *2 。以下は、複数のIMUを同時に計測して比較したグラフです。なかなかうまく取れています。 aptpodがintdashという形で遠隔監視を提供するとこのような形になります。しかし、T1にせよほかの機器にせよ、GPSのデータを見るためだけにintdashのシステムを使うのはコストが高いのでおススメできません。 そこでほかの方法として、u-centerを利用する方法を紹介します。 u-center をネットワーク越しで利用する T1に搭載されているGPSのモジュールは、u-blox社のNEO-M8シリーズになります。u-bloxは、Windows用にu-centerというGUIツールを提供しています。 www.u-blox.com 通常、このツールはWindows PCにUSBなどで接続したモジュールとシリアル通信してデータを見ることが多いです。T1の開発中にGPS設定の調査などを行う際には、これを使いたかったので、基板を改造してWindowsからシリアル通信できるようにしました。 ネックになるのは 基板を改造して という部分です。外側に出すかLinux側に流すかをDIPスイッチで切り替えられるようにする必要もあり、あくまで試験用途だったので試作基板1枚分しか作っていません。 ser2netでシリアルデータをネットワーク上に流す そこで、ser2netという便利なものを使います。これを使うと、Telnet/TCP経由でシリアルポートの先にあるデバイスと通信できるようになります。 linux.die.net 今回の例でいえば、T1に搭載されているGPSモジュールに対して、Windows PCからネットワーク経由でシリアル通信する、という形になります。 ser2netのインストール T1の場合であれば、aptコマンドで簡単にインストール可能です。 $ sudo apt update $ sudo apt install ser2net ser2netの設定 使っている環境に合わせて設定を変更します。T1の場合、初期値はボーレート 9600 であるため、そのように設定します。 $ cat /etc/ser2net.conf 2000:raw:600:/dev/ttyTHS1:9600 8DATABITS NONE 1STOPBIT banner 設定出来たら、ser2net のサービスを起動します。 $ sudo systemctl restart ser2net これだけでGPSのデータがネットワーク上に流れていきます。例えば、Tera Termを使うとこのような形でデータを見ることができます。 u-center を使ってGNSSデータを見る では、実際にWindows上でu-centerを利用してみます。 u-centerのインストール M10用にu-center 2というものも存在していますが、T1に搭載されているのはM8シリーズですので無印のu-centerを利用します。 u-centerの設定 u-center起動後、Receiver -> Connection -> Network connectionを選択し、先ほどser2netで設定した内容を追加します。ここでは私が使用しているT1 のIPアドレスが 192.168.11.43 、設定したポートが 2000 なので、そのように設定します。 この設定を行うだけで、u-centerがネットワークを経由してT1のGPSモジュールの情報を表示できるようになります。 シリアルで直接つなげたものと比較してもそん色ないように思います。 また、u-bloxのモジュールは詳細な設定を行うために、UBX Protocolという独自プロトコルで通信をしてメッセージのやり取りを行う必要があり、これが若干面倒です。Linux向けだと ubxtool というものもありますが、少し凝った設定を行おうとすると仕様書とにらめっこしてコマンドを調べる必要があります。その点、u-centerであれば、GUI上で設定項目を眺められるため、目当ての設定やデータが扱いやすくなります。この点も、この方式で実施するメリットの一つと言えるでしょう。 おわりに aptpod では基本的に自社のサービスを使用してデータの可視化を行っていますが、開発での試験やお客様のニーズなど、状況に合わせて汎用的な方法を取ることもあります。いろいろな手法を試すことで知見もたまり、自社製品にフィードバックできることもあります。 T1の活用方法についてはテックブログだけではなく、 デベロッパーガイド という形でもいろいろご紹介しているので、興味を持った方はぜひそちらも見てみてください。まずT1の詳細を知りたい、という方は以下の紹介ページでご確認ください。 www.aptpod.co.jp *1 : Inertial Measurement Unit、慣性計測ユニット *2 : 試験用に機能実装したもので、2022/5 時点ではintdash Edgeには組込まれていません
製品開発グループintdashチームの呉羽です。 今回は標準化が進められているWebTransportの紹介と、実際にブラウザでの動作検証を行います。 本記事の参考資料として、Webの標準化団体W3C(World Wide Web Consortium)が公開している WebTransport Explainer を用いています。 WebTransportとは何か WebTransportとは、ブラウザとサーバー間での利用を目的とした新しい双方向通信プロトコルです。ではなぜ今さら双方向通信プロトコルなのでしょうか? WebTransport標準化の目的 まず現状の課題をお伝えします。Web上の双方向通信プロトコルとしてWebSocketが存在しますが、WebSocketは単一のTCPコネクション上で動作します。ゆえにTCPによるメッセージの到達保証と順序保証が提供されますが、それらが不要なユースケースに対してはオーバスペックです。 そこでWebTransportでは、到達保証・順序保証のあるWebSocketのような通信方法に加え、それらの保証が不要な通信方法の提供を目的としています。 WebTransport ExplainerにはWebTransportの目的として以下の3つが設定されています。 Provide a way to communicate with servers with low latency, including support for unreliable and unordered communication. Provide an API that can be used for many use cases and network protocols, including both reliable and unreliable, ordered and unordered, client-server and p2p, data and media. Ensure the same security properties as WebSockets (use of TLS, server-controlled origin policy) 先に述べた通信方法の提供に加え、従来のWebSocketと同じセキュリティ水準が必要です。 WebTransportを実現する技術 WebTransportの実装として、HTTP/3を利用した仕様が策定されています。 https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-02.html HTTP/3はQUICのコネクション上で動作するため、QUIC Datagram[RFC9221]による到達保証が不要な通信を提供できます。またQUICのストリームという抽象化された通信経路により、ストリーム間での順序保証が不要な通信を実現できます。 以下に、HTTP/3でのWebTransportセッションの確立・メッセージのやり取りを要約します。 HTTP/3の :protocol ヘッダーに webtransport を指定し、拡張CONNECTメソッドでサーバーにリクエストを送る。 1に用いたQUICのストリームIDを、WebTransportのセッションIDとする。 信頼性が必要な通信をする場合は、QUICのストリームを開き、セッションIDとともに送信する。 信頼性が不要な通信をする場合は、DatagramのメッセージをセッションIDとともに送信する。 このように実装のほどんどはQUICの機能を利用するため、WebTransportの実装にはそれほど手は掛からなさそうです。 WebRTCという代替案 WebSocket以外にもWebRTCという双方向通信プロトコルが存在します。 web.devが公開している記事 では、以下のようにWebRTCと比較しています。 WebRTCはサーバー側でICE, DTLS, SCTPといった珍しいプロトコルの実装が必要だが、WebTransportはQUICやHTTP/3のみでよい(ただしWebTransportは実装方法による)。 WebRTCはWeb Worker内で動かないが、WebTransportは動く。 WebRTCよりもWebTransportはモダンなAPIデザインである。 しかし、既にWebRTCで満足のいくアプリケーションが実装されている場合、WebTransportに変更する大きなメリットは無いそうです。 WebTransportをブラウザ上で動かしてみる それでは実際にWebTransportを実装し、ブラウザ上で動かしてみます。今回は弊社のサーバーサイドチームが主に用いているGo言語で実装します。 Go言語では既に quic-go と、それを用いた webtransport-go というライブラリが存在しています。しかしWebTransportに関する仕様は未だに不安定であるため、すべての機能は実装されていません。例えばHTTP上でDatagramを扱う仕様( draft-ietf-masque-h3-datagram )は過去に互換性のない変更が起きており、それが影響してかquic-goにも実装されていません。 よって、安定していそうな箇所やWebTransportに依らない箇所は quic-goへPullRequestを出し 、他の箇所は自己実装して進めます。 実装したGoのWebTransportライブラリ: github.com/hareku/webtransport-go ブラウザ側のコードは、 GoogleChromeが公開しているサンプルコード を一部修正して利用します。 修正したコード: github.com/hareku/samples/tree/aptpod-webtransport/webtransport それでは、WebTransportサーバーとクライアント側のサーバーを起動してみます。動作にはGo v1.18とNode.js v14.18.0を用います。これらのバージョンが異なる場合、動作しない場合があります。 $ git clone https://github.com/hareku/webtransport-go.git -b aptpod-webtransport $ cd webtransport-go/cmd/echo $ go run . cert.pem key.pem # localhostの自己証明書を用意して指定 $ git clone https://github.com/hareku/samples.git -b aptpod-webtransport $ cd samples/webtransport $ ./launch.sh GoogleChromeが公開しているWebTransportのブラウザ検証用画面 簡素な検証にはなりますが、全ての通信方法で「Hello」というメッセージを送信し、同一メッセージが返ってくることを確認します。 データグラム Sent datagram: Hello Datagram received: Hello 片方向ストリーム Sent a unidirectional stream with data: Hello New incoming unidirectional stream #1 Received data on stream #1: Hello Stream #1 closed 双方向ストリーム Opened bidirectional stream #2 with data: Hello Received data on stream #2: Hello Stream #2 closed ブラウザ上のイベントログを見ると、同一メッセージが返ってくることを確認できました。 まとめ 以上がWebTransportの紹介と動作検証でした。WebTransportはまだRFCとして公開はされていませんが、WebSocketの次世代である双方向通信プロトコルとして弊社は期待しています。
はじめに ソリューションプロフェッショナルグループのみよしです。 ROSソリューションの担当をしています。 今回はROSでrosbridge_serverの処理速度改善を試した結果についてご紹介します。 はじめに intdash ROS Bridge ROS Bridge Server 高頻度データに対する問題点 ROS(Melodic)でrosbridge_tcpの速度改善 rosbridge_tcp 改善策 計測と結果 環境 計測する時間 テスト用のクライアント (test_client_tcp.py) 手順 結果 - incoming_bufferの設定なし 結果 - incoming_buffer=1024 ROS(Noetic)でrosbridge_tcpを試す 環境 結果 - incoming_bufferの設定なし 結果 - incoming_buffer=1024 rosbridge_websocketを試す 環境 テスト用のクライアント (test_client_ws.py) 手順 結果 - Melodic 結果 - Noetic まとめ intdash ROS Bridge 弊社が提供する DX Functions の 遠隔制御 のユースケースとして、ROSを利用したロボット開発が挙げられます。 これらのユースケースに対して、弊社ではROSメッセージの遠隔リアルタイムデータ伝送を行うintdash Bridge / intdash ROS2Bridgeというプロダクトを提供しています。 intdash Bridge / intdash ROS2Bridgeを使うことで、遠隔地のROS1 / ROS2空間をつなぎ、ROSのメッセージをやり取りすることによる遠隔制御やモニタリングなどのユースケースが実現できます。 intdash Bridge デベロッパーガイド intdash ROS2Bridge デベロッパーガイド 弊社の過去Blogでも、intdash Bridge / intdash ROS2Bridge、rosbridge_serverに関してのご紹介をしてきました。 tech.aptpod.co.jp tech.aptpod.co.jp tech.aptpod.co.jp intdash Bridge / intdash ROS2Bridgeは、intdash Edge Agentを介してintdash Serverに接続することが可能です。 (下記は一例です) ROS Bridge Server これらの弊社製品とは別に rosbridge_suiteのrosbridge_server を使用する場合もあります。 rosbridge_serverは、rosbridge protocolを使用するクライアントであれば、接続することが可能です。 rosbridge protocolを使用するクライアントからrosbridge_serverへ接続するので、LANやVPN等でつながっている、もしくはrosbridge_serverが動く環境でグローバルIPを有している場合に使用可能です。 (下記は一例です) rosbridge_serverの通信方式として、ROS1では、TCP・UDP・WebSocketが提供されています。 rosbridge_tcp rosbridge_udp rosbridge_websocket また、ROS2では、現在、WebSocketのみ提供されています。 *1 rosbridge_websocket 高頻度データに対する問題点 過去のROS環境向けの開発でも、rosbridge_serverを度々使用していますが、高頻度のデータでは処理速度が遅くなり、その影響がrosbridge_serverの後段にあるノードやクライアント側にも生じるという問題がありました。 例えば、下記計測で行なっている環境(条件)では、テスト用クライアントでs.send()の後にtime.sleep()をして、送信する間隔を調整した場合、time.sleep()が0.0001secよりsleep時間が短くなると、rosbridge_serverを起動している側で行うrostopic echoの表示が明らかに遅れ、クライアント側でも送信待ち状態が生じます。 (rosbridge_serverで受信するメッセージ(サイズや要素数)によって、送信する間隔がどれぐらいであれば影響を受けないかは異なります。) ROS(Melodic)でrosbridge_tcpの速度改善 rosbridge_tcp 最初に結論を述べると、Melodic(Python2.7)ではProtocolクラスのincoming()内で、文字列の連結を + から join() とすることで処理速度が改善できました。 Pythonの文字列連結については、 こちらの “String Concatenation” に記載されています。 RosBridgeTcpSocketクラスのhandle()内で、recv()したデータの処理が終わるまで次recv()が行なわれないので、一度にrecv()するデータのサイズが大きくなると、recv()後に行なう処理で時間が掛かる為、遅くなります。 構成として SocketServerのTCPServerを使用 TCPServerでrequest_queue_size=5と設定 RosbridgeTcpSocketでincoming_buffer=65536byte(64k)と設定 となっているので、MAX 64k x 5 = 320k 分のデータが溜まる可能性があります。 incoming_bufferは起動時にパラメーターとして変更可能です。 改善策 rosbridge_library/src/rosbridge_library/protocol.py で文字列の連結をしている箇所を変更します。(下記diff参照) rosbridge_tcpが動く環境のスペックによってはincoming_bufferの値を小さくします。 diff --git a/rosbridge_library/src/rosbridge_library/protocol.py b/rosbridge_library/src/rosbridge_library/protocol.py index c9424ad..3ca2c47 100644 --- a/rosbridge_library/src/rosbridge_library/protocol.py +++ b/rosbridge_library/src/rosbridge_library/protocol.py @@ - 124 , 10 + 124 , 11 @ @ class Protocol: message_string -- the wire-level message sent by the client """ - if self.bson_only_mode: - self.buffer.extend(message_string) - else: - self.buffer = self.buffer + str(message_string) + if message_string != "": + if self.bson_only_mode: + self.buffer.extend(message_string) + else: + self.buffer = ''.join([self.buffer, str(message_string)]) msg = None # take care of having multiple JSON-objects in receiving buffer 計測と結果 実際に、改善策の効果を計測します。 計測の方法として DockerのContainerで動かすrosbridge_serverに対して、Hostからテスト用のクライアントでメッセージ 100,000 件を送信する。 rosbridge_serverで受信したメッセージはpublishされるので、rostopic echoで表示する。 ということを行ないます。 環境 DockerのContainerを使用(下記イメージを使用) *2 $ docker pull ros:melodic-robot 計測する時間 メッセージ送信開始(クライアント起動)から、メッセージ送信終了(クライアント終了)まで。 メッセージ送信開始(クライアント起動)から、rosbridge_serverを起動している側(DockerのContainer)でrostopic echoを行い、送信したメッセージの表示が終了するまで。 テスト用のクライアント (test_client_tcp.py) #!/usr/bin/env python3 import sys import socket import time import json def advertise_msg (topic, type ): return json.dumps( dict (op= 'advertise' , topic=topic, type = type )).encode( 'utf-8' ) def publisher_msg (topic, data): return json.dumps( dict (op= 'publish' , topic=topic, msg=data)).encode( 'utf-8' ) def publish (host= None , port= None , topic= None ): if not host or not port or not topic: print ( 'invalid params host={0} port={1} topic=({2}, {3})' .format( host, port, topic[ 'name' ], topic[ 'type' ])) try : s = socket.socket() s.connect((host, port)) s.send(advertise_msg(topic=topic[ 'name' ], type =topic[ 'type' ])) time.sleep( 0.5 ) cnt_max = 100000 for cnt in range (cnt_max): data = dict (data= 'hello, {0}, {1}' .format(cnt, time.time())) s.send(publisher_msg(topic[ 'name' ], data)) print (data) except KeyboardInterrupt : pass except Exception as e: print (e) finally : s.close() if __name__ == '__main__' : host = '127.0.0.1' port = 9090 topic = { 'name' : '/chatter' , 'type' : 'std_msgs/String' } if len (sys.argv) > 1 : host = sys.argv[ 1 ] publish(host, port, topic) 手順 DockerのContainerでrosbridge_serverを起動する。 incoming_bufferの設定なし $ roslaunch rosbridge_server rosbridge_tcp.launch incoming_buffer=1024 $ roslaunch rosbridge_server rosbridge_tcp.launch incoming_buffer:=1024 DockerのContainerでrostopic echoを行なう。 $ rostopic echo /chatter Hostでテスト用のクライアントを起動する。 $ python3 test_client_tcp.py 172.17.0.2 結果 - incoming_bufferの設定なし 文字列の連結 メッセージ送信開始~メッセージ送信終了 メッセージ送信開始~rostopic echoの表示終了 変更なし 01h 12m 14.94s 02h 45m 22.59s 変更あり 00h 01m 30.16s 00h 04m 15.50s 結果 - incoming_buffer=1024 文字列の連結 メッセージ送信開始~メッセージ送信終了 メッセージ送信開始~rostopic echoの表示終了 変更なし 00h 03m 26.07s 00h 03m 37.23s 変更あり 00h 00m 13.99s 00h 00m 16.06s 文字列の連結をしている箇所の変更で大きく変わりました。 計測した環境がDockerのContainerで非力な為、incoming_bufferを小さくすることで更に効果が得らました。 ROS(Noetic)でrosbridge_tcpを試す 先のMelodicで行なった変更を試してみました。 テスト用のクライアント、手順はMelodicと同様です。 環境 DockerのContainerを使用(下記イメージを使用) $ docker pull ros:noetic-robot 結果 - incoming_bufferの設定なし 文字列の連結 メッセージ送信開始~メッセージ送信終了 メッセージ送信開始~rostopic echoの表示終了 変更なし 00h 01m 59.37s 00h 04m 15.79s 変更あり 00h 01m 28.01s 00h 04m 09.26s 結果 - incoming_buffer=1024 文字列の連結 メッセージ送信開始~メッセージ送信終了 メッセージ送信開始~rostopic echoの表示終了 変更なし 00h 00m 10.53s 00h 00m 11.87s 変更あり 00h 00m 10.45s 00h 00m 11.90s Melodicでの結果とは異なり、文字列の連結をしている箇所の変更をしても大した違いは見られませんでした。 rosbridge_suiteはPythonで作られているので、PythonのVersionの違いが影響していると思われます。(MelodicはPython2.7、NoeticはPython3) incoming_bufferを小さくすることについては、効果が得られました。 rosbridge_websocketを試す 構成として autobahnのWebSocketServerFactoryを使用 TornadoのWebSocketHandlerを使用 となっています。 環境 DockerのContainerを使用 テスト用のクライアント (test_client_ws.py) #!/usr/bin/env python3 import sys import json import time import asyncio from autobahn.asyncio.websocket import (WebSocketClientProtocol, WebSocketClientFactory) class TestClientProtocol (WebSocketClientProtocol): def onOpen (self): self._sendDict({ 'op' : 'advertise' , 'topic' : '/chatter' , 'type' : 'std_msgs/String' , }) time.sleep( 0.5 ) self._sendDictLoop() def _sendDict (self, msg_dict): msg = json.dumps(msg_dict).encode( 'utf-8' ) self.sendMessage(msg) cnt = 0 cnt_max = 100000 def _sendDictLoop (self): data = 'hello, {0} {1}' .format(self.cnt, time.time()) msg_dict = { 'op' : 'publish' , 'topic' : '/chatter' , 'msg' : { 'data' : data } } msg = json.dumps(msg_dict).encode( 'utf-8' ) self.sendMessage(msg) print (data) self.cnt += 1 if self.cnt >= self.cnt_max: return self.factory.loop.call_later( 0 , self._sendDictLoop) def onMessage (self, payload, binary): self.__class__.received.append(payload) if __name__ == '__main__' : host = '127.0.0.1' port = 9090 if len (sys.argv) > 1 : host = sys.argv[ 1 ] url = 'ws://{0}:{1}' .format(host, port) factory = WebSocketClientFactory(url) factory.protocol = TestClientProtocol loop = asyncio.get_event_loop() coro = loop.create_connection(factory, host, port) loop.run_until_complete(coro) loop.run_forever() loop.close() 手順 DockerのContainerでrosbridge_serverを起動する。 $ roslaunch rosbridge_server rosbridge_websocket.launch DockerのContainerでrostopic echoを行なう。 $ rostopic echo /chatter Hostでテスト用のクライアントを起動する。 $ python3 test_client_tcp.py 172.17.0.2 結果 - Melodic 文字列の連結 メッセージ送信開始~メッセージ送信終了 メッセージ送信開始~rostopic echoの表示終了 WebSocket 変更なし 00h 00m 05.18s 00h 00m 16.20s WebSocket 変更あり 00h 00m 04.99s 00h 00m 15.50s TCP 変更あり(incoming_buffer=1024) 00h 00m 13.99s 00h 00m 16.06s 結果 - Noetic 文字列の連結 メッセージ送信開始~メッセージ送信終了 メッセージ送信開始~rostopic echoの表示終了 WebSocket 変更なし 00h 00m 04.63s 00h 00m 06.17s WebSocket 変更あり 00h 00m 04.49s 00h 00m 06.05s TCP 変更あり(incoming_buffer=1024) 00h 00m 10.45s 00h 00m 11.90s WebSocketが、文字列の連結をしている箇所の変更あり・なしに関わらず速いのは、常に1メッセージずつ処理を行なっているからだと思います。 まとめ 本記事では、Melodicでのrosbridge_serverの処理速度改善と、NoeticやWebSocketとの比較を行いました。 rosbridge_tcpを使用する場合、Melodicでは文字列の連結をしている箇所を変更することで大きく処理速度を改善できました。 同様の変更をNoeticで行っても、大きな効果は得られませんでしたが、それはPythonのVersionの違いによるものだと思われます。 rosbridge_tcpが動く環境のスペックによって、incoming_bufferを小さくすることはMelodic・Noeticともに効果はありました。(潤沢なスペックの場合、大きな効果は得られない可能性があります。) 文字列の連結をしている箇所の変更は、rosbridge_tcp・rosbridge_websocketともに使用している箇所ですが、変更なしのMelodicでもrosbridge_websocketが速いのは、常に1メッセージずつ処理を行なうからだと思います。 可能であるなら、rosbridge_websocketを使用するのが良いと思います。 弊社ではROSコミュニティへの貢献もしていきたいと思っております。 上記も含めて弊社製品、またはROSに関連した開発に興味を持って頂けた方は是非、こちらの 弊社採用ページ もご覧ください。 製品に関するお問い合わせはこちらへ! www.aptpod.co.jp 最後までご覧いただきありがとうございました。 *1 : v1.1.1(2021-12-09)のリリースでTCP・UDPは削除されました。 *2 : HostのCPUはIntel(R) Core(TM) i7-10870H CPU @ 2.20GHz、メモリは64Gです。
こんにちは。Visual M2M Data Visualizer の製品開発を担当している白金です。 以前に、「 WebCodecs の VideoDecoder を使用してH.264の動画を再生してみた 」の記事を紹介させていただきました。 tech.aptpod.co.jp その後、弊社が提供する Visual M2M Data Visualizer に含む標準ビジュアルパーツ「Video Player パーツ」に WebCodecs の VideoDecoder を適用した結果、複数の課題が改善できましたので、改善結果と苦労の軌跡について紹介したいと思います。 Visual M2M Data Visualizer を利用した動画ストリーミング再生に興味がある方、または WebCodecs の VideoDecoder を利用中・利用したい方のお役に立てたら嬉しいです。 WebCodecs について気になる方は、上記ブログで紹介しておりますのでご参照ください。(当ブログでは WebCodecs の説明については割愛します。) 改善した結果 Video Player パーツとは? 改善前の Video Player パーツ アーキテクチャー 発生していた課題 改善後の VideoPlayer パーツ アーキテクチャー 課題は解決できた? 苦労の軌跡 1フレームのデコード遅延が発生する パフォーマンス低下時に、動画が緑色に塗りつぶされて表示される LIVE 動画の再生時に、ネットワーク通信状況が不安定になるとパフォーマンスが低下する 過去動画の再生時に、デコードされた動画フレームが使用するメモリで圧迫する おわりに 改善した結果 まずは改善した結果について、5つの項目について紹介したいと思います。 動画再生の安定化、遅延の改善、及び再生中の動画情報の表示が可能になった、など以下の課題が解決できました。 特定のH.264 エンコーダーとの組み合わせで発生していた動画再生の不安定、カクつきが解消した。 LIVE 動画 15fps の再生で約70〜200msの遅延を改善し、iOS向けアプリ の Stream Video の遅延とほぼ同じになった。 Google Chrome の詳細設定で「ハードウェアアクセラレーションを使用可能な場合は使用する」を OFF にする運用が不要となり、運用コスト削減に繋がった。 再生している時刻の動画フレームのタイムスタンプの表示が可能になった。 再生している動画のコーデック、解像度、フレームレートなどの情報の表示が可能になった。 2. LIVE 動画再生の遅延比較 4., 5. タイムスタンプ、Video Info の表示 では、Video Player パーツの改善前後について詳細な情報を紹介していきたいと思います。 ボリュームが多いですが、最後までお付き合いいただけると嬉しいです。 Video Player パーツとは? Video Player パーツは、弊社が提供する Visual M2M Data Visualizer に含む標準ビジュアルパーツの一つです。 計測した H.264 の動画フレームを弊社のintdashサービスを経由して LIVE ストリーミング再生、またはストアした動画を後から動画再生するための機能として提供しています。 Video Player パーツ また、Visual M2M Data Visualizer のダッシュボードで同じ時刻の計測データの値と動画の再生時刻を同期させて再生することも可能です。 Video Player パーツを含むダッシュボード その他の表現については、 Visual M2M Data Visualizer でご確認いただけます。 改善前の Video Player パーツ アーキテクチャー LIVE 動画の再生は、H.264 の動画フレームを Google Chrome (M93以前) では直接表示する方法が API で提供されていなかったため、Media Services を経由して、Fragmented MP4 のコンテナ形式に変換し、 Media Source Extension API を使用して再生していました。 改善前の LIVE 動画の再生 Fragmented MP4 については、下記記事でも紹介されていますので当ブログでは説明を割愛します。 qiita.com 過去動画の再生は、HLS コンテナ形式でストアされた動画情報を、 hls.js の SDK を使用していました。 改善前の過去動画の再生 発生していた課題 上記アーキテクチャーの実装では以下4つの課題が発生していました。 (一部、前回の ブログ から再掲になります。) 課題1 - LIVE 動画再生時の動画フレームのタイムスタンプの判別 モバイル回線など電波状況により著しく通信速度が低下するケースがある環境では、H.264 の動画フレームがリアルタイムで intdash Core Services に届かず、 Fragmented MP4 の動画フレームが欠損した状態になるケースがありました。 正確なタイムスタンプが判別できない 課題2: LIVE 動画再生時のGoogle Chrome で動作する H.264 デコーダーの遅延 弊社で扱うことができる動画フレームは、特定の H.264 エンコーダー には限定していませんが、H.264 エンコーダーと Google Chrome が使用するハードウェアアクセラレーションの組み合わせによって、デコード対象となる動画フレームを十分に確保しないと、デコード後の動画フレームが出力されないケースがありました。 そのため、低遅延で動画をストリーミングするケースでは、動画の再生が安定しない課題が発生していました。 ハードウェアアクセラレーションを使用したときのデコーダー遅延の課題 暫定対策として、Google Chrome 詳細設定の「ハードウェア アクセラレーションが使用可能な場合は使用する」を OFF に変更することで、 ソフトウェアデコーダーを使用するようになりデコーダー遅延は解消できました。 しかし、事前に Google Chrome の設定を変更する必要がある運用コストの課題は未解決の状態になっていました。 H.264 デコーダー遅延の暫定対策 課題3 - LIVE 動画再生時の動画フレームの受信が詰まったときの再生位置のケア Google Chrome を使用して Fragmented MP4 を再生するときは、動画フレームに含まれる timescale や duration の情報を使用して、各フレームを実時間に沿って再生します。 intdash Core Services から H.264 動画フレームを受信するときに、一時的な通信速度の低下が発生すると、動画フレームの受信が詰まるケースがあり、動画再生は一時的に停止する症状が発生します。 その後、通信速度が復帰した後に、詰まっていた動画フレームを短時間に一度に取得することも想定されますが、その際、詰まっていた動画フレームが順番に再生されることで再生可能な動画フレームが余分に生じてしまい、LIVE 動画の再生遅延が発生する要因となっていました。 暫定対策として、現在の動画の再生時刻と、まだ再生していない再生可能な動画の最後の時刻を定期的に監視することで、一定以上再生可能なフレームあるときは、再生位置を再生可能な最後のフレームの位置までシークする補正を適用していました。 動画再生位置を最新のフレームで維持するための補正 課題4 - 過去動画再生時の HLS 動画のセグメント内の動画フレームのタイムスタンプの補正 intdash で扱う100ミリ秒∼1ミリ秒間隔程度の高頻度で発生する時系列データでは、データの転送遅延とタイムスタンプによる忠実な再生に関する課題を解決する必要があります。 動画の再生についても、計測した動画のタイムスタンプに忠実に再生することで、100ミリ秒∼1ミリ秒間隔程度の高頻度で計測した他のセンサーの値と同期して確認、及び解決したいケースがあります。 また、Media Services では過去再生で使用する動画として HLS コンテナ形式を採用しており、HLS コンテナ形式に含む一定間隔で区切られた各セグメントファイル内に含む動画フレームのタイムスタンプは、等間隔のタイムスタンプで再配置される仕様になっています。 そのため、動画のタイムスタンプに忠実に再生、確認したいケースを満たせないケースがありました。 *1 HLS のセグメントに変換された後の 動画フレームのタイムスタンプ 改善後の VideoPlayer パーツ ここまで、改善前の Video Player パーツの課題について紹介させていただきました。 アーキテクチャー まずは、改善後のアーキテクチャーの変更点について紹介したいと思います。 Google Chrome M94 で導入された WebCodecs の VideoDecoder を使用することで、直接 H.264 動画フレームを利用することが可能になり、構成がシンプルになりました。 改善結果は下記のとおりです。 (当ブログの冒頭に記載した改善結果と同じ内容です。) 特定のH.264 エンコーダーとの組み合わせで発生していた動画再生の不安定、カクつきが解消した。 LIVE 動画 15fps の再生で約70〜200msの遅延を改善し、iOS向けアプリ の Stream Video の遅延とほぼ同じになった。 Google Chrome の詳細設定で「ハードウェアアクセラレーションを使用可能な場合は使用する」を OFF にする運用が不要となり、運用コスト削減に繋がった。 再生している時刻の動画フレームのタイムスタンプの表示が可能になった。 再生している動画のコーデック、解像度、フレームレートなどの情報の表示が可能になった。 改善後の LIVE 動画の再生 改善後の過去動画の再生 課題は解決できた? WebCodecs の VideoDecoder を使用することで、改善前に発生していた課題が全て解決できました! VideoDecoder で利用した機能は以下2つです。 動画フレームをデコードするときに指定したタイムスタンプは、デコードされた動画フレームに引き継がれる。 VideoDecoder でデコードする事前準備でハードウェアアクセラレーションを使用しない設定を指定することができる。 以下、Video Decoder を使用したサンプルコードです。 const videoDecoder = new VideoDecoder ( { ... output: ( videoFrame ) => { // 1... decodeFrame で指定した timestamp 123456789 が参照可能。 console .log ( videoFrame.timestamp ) } , } ) videoDecoder.configure ( { ... // 2... ハードウェアアクセラレーションを使用しない設定をする。 hardwareAcceleration: 'prefer-software' , } ) videoDecoder.decode (new EncodedVideoChunk ( { type : 'key' , // 1... 動画フレームに紐づくタイムスタンプを指定する。 timestamp: 123456789 , data: encodedVideoFrameData , } )) では、各課題の解決方法について見ていきましょう。 課題1 - LIVE 動画再生時の動画フレームのタイムスタンプの判別 WebCodecs の VIdeoDecoder でデコードする前の動画フレームのタイムスタンプは、デコードされた動画フレームでも利用ができます。 この機能を利用して、intdash を使用して計測する H.264 動画フレームのタイムスタンプをそのまま使用することで、再生時刻のタイムスタンプを表示することが可能になりました。 課題1. タイムスタンプの表示 課題2 - LIVE 動画再生時のGoogle Chrome で動作する H.264 デコーダーの遅延 H.264 デコーダーの遅延については、改善前の状態でも「Google Chrome の詳細設定でハードウェアアクセラレーションを使用可能な場合は使用する」を OFF に変更することで暫定対策は実現できていました。 また、 VideoDecoder でデコードする事前準備でハードウェアアクセラレーションを使用しない設定を指定することができる。 から、Google Chrome の詳細設定を変更することなく Video Player パーツで「ハードウェアアクセラレーションを使用しない」ように自動で設定することが可能となり、暫定対策は廃止、及び運用コストの削減することができました。 課題2. Google Chrome の詳細設定の変更が不要 課題3 - LIVE 動画再生時の動画フレームの受信が詰まったときの再生位置のケア 改善前は、Fragmented MP4 と、Media Source Extension API を使用した動画再生で、最新の動画フレームの再生位置を表示するケアが必要でした。 WebCodecs の VideoDecoder を利用することによって、デコードされた最新の動画フレームが更新される毎に、デコードされた動画フレームを Video Player パーツで表示するだけのシンプルな構成に改善することができました。 最新の動画フレームを表示するための補正は不要 課題4 - 過去動画再生時の HLS 動画のセグメント内の動画フレームのタイムスタンプの補正 課題1の改善と同様に、H.264 の動画フレームのタイムスタンプをデコードされた動画フレームでも利用可能になりました。 その結果、再生したい時刻とデコードされた動画フレームのタイムスタンプを使用して忠実に再生できるようになりました。 課題4. 過去動画の再生時のタイムスタンプ 苦労の軌跡 さて、上記のとおり、複数の課題が解決できて喜ばしい限りなのですが、実現するためには何度も壁にぶち当たり、一つずつ解消していきました。 以降は、WebCodecs の VideoDecoder を使用する上で解決していった苦労の軌跡について紹介したいと思います。 1フレームのデコード遅延が発生する 15fps など 秒間に複数の動画フレームの再生では気づかなかったのですが、1fps の動画フレームでテストしているときに、動画の再生遅延が1秒遅れの症状になっていることに気づきました。 調査を進めた結果、WebCodecs の VideoDecoder で 動画フレームをデコードするときに、動画フレームが 1フレーム遅れでデコードされていることがわかりました。 1フレーム遅れでデコードされた動画フレームが出力される 当症状については VideoDecoderConfig で、下記 optimizeForLowLatency を有効にすることで解決できました。 videoDecoder.configure ( { ... optimizeForLowLatency: true , } パフォーマンス低下時に、動画が緑色に塗りつぶされて表示される Windows 環境でテストしていると、デコードされた動画フレームの画像が緑色で塗りつぶされるケースが発生しました。 調査を進めた結果、デコードされた動画フレーム VideoFrame を OffscreenCanvas の drawImage で表示している箇所に原因があるとわかりました。 Visual M2M Data Visualizer のダッシュボードで Video Player パーツを複数表示、またはその他のビジュアルパーツを同時に表示したときなど、Google Chrome のパフォーマンスが低下した際に当症状が発生しやすい状況になっていました。 OffscreenCanvas を使用した動画フレームの描画 対策として、描画方法を OffscreenCanvas から MediaStreamTrackGenerator で使用可能な WritableStream.getWriter に置き換えることで当症状を解決しました。 MediaStreamTrackGenerator を使用した動画フレームの描画 LIVE 動画の再生時に、ネットワーク通信状況が不安定になるとパフォーマンスが低下する LIVE 動画再生時にネットワークの通信速度の低下から復帰した際に、詰まっていた H.264 動画フレームを実再生時間より短い間隔で一度に受信するケースがあります。 その場合、デコードするための H.264 動画フレームのキューが想定以上に増加する可能性があります。 全て1フレームずつ逐次動画デコードする場合、必要以上にデコード処理コストの負荷が上昇し、パフォーマンスの低下、動画の再生遅延に影響します。 対策として、デコードする前の動画フレームのキューでキーフレームが含まれている場合は、キューに含む最新のキーフレームより古い動画フレームを破棄することで、デコード処理コストの削減を実現しました。 動画再生に不要なキーフレームより古い動画フレームは破棄 過去動画の再生時に、デコードされた動画フレームが使用するメモリで圧迫する LIVE 動画の再生のときは、最新の1フレームの動画のみ表示すればよかったので、過去の動画フレームは破棄する運用で問題ありませんでした。 過去動画の再生では、時系列データを意識して複数のタイムスタンプに相当する動画フレームを管理、表示する必要があります。 そこで、最も気をつける必要がある対象が、 デコードされた動画フレームのメモリ管理 になります。メモリの使用量が増加すると、Google Chrome のパフォーマンスの低下、またはブラウザのタブがクラッシュするなどの症状が発生します。 デコードされた動画フレームの ByteSize は、表示する動画の解像度に比例します。 デコード後の動画フレームの概算 ByteSize 以上から、次のポイントを気をつけるようにしました。 一度にデコードする動画フレームの流量を制限し、デコードされた動画フレームの数は必要最小限に抑える。 再生している間は、動画フレームを差分で順番にデコードし、デコードされた動画を順次に表示する。 再生時刻を巻き戻すなどシーク位置を変更した場合は、移動後の時刻を基準に直前のキーフレームから動画フレームをデコードをやり直す。 デコード実施前の動画フレームは、現在の再生時刻を含む1分未満の範囲のみメモリにキャッシュし、それ以外は都度 intdash Core Services から取得し直す。 構成は下記のようになりました。 過去動画再生時の構成 結果、動画再生で必要なメモリ使用量を 250MB 以内に抑えることができ、パフォーマンスが低下することなく再生できるようになりました。 おわりに WebCodecs の VideoDecoder を使用して、Video Player パーツ の課題を改善し、より使いやすい機能としてアップデートできたと実感しています。 また、メジャーバージョン級の開発内容の更新で良い開発経験ができ達成感を感じることができました。 Visual M2M Data Visualizer は開発をスタートしてからの期間が長い製品ではありますが、今回のように Web の新しい技術も取り込みつつ改善ができる領域は多く、これからも積極的に改善を推進できればと考えています。 上記も含めて弊社製品、またはフロントエンドに興味を持って頂けた方は是非、こちらの 弊社採用ページ もご覧ください。 製品に関するお問い合わせはこちらへ! www.aptpod.co.jp *1 : HLS 形式コンテナに含む各セグメントの先頭のフレームのタイムスタンプは H.264 の動画フレームと一致するため、正確なタイムスタンプを意識しなければ気になることはありません。
こんにちは! コーポレート・マーケティング室、デザインチームの「チェン ・ルイ」と申します。 普段は社内製品のアプリケーションのUIデザイン業務を行なっています。 現在、アプトポッド製品UIのデザインシステム運用の一環として、デザインガイドラインを明文化するツール、「Design Document Tool」を検討しています。 今回は検証した3つのツールを紹介します。 Zeroheight InVision DSM Confluence 着眼点はデザインガイドラインの管理の課題から「Design Document Tool」にたどり着くまでに、上記の3つのツールをを使用した感想です。 (具体的な操作方法を割愛します。) 「Design Document Tool」選定の参考になれば幸いです。 【背景・問題】 【デザインガイドラインをSketchでまとめてみた】 Sketchの制約 【Design Document Toolを導入したい】 Design Document Toolの選択基準 【ツール検証1: Zeroheight】 特徴1: デザインデータをアップロードしてそのままドキュメント化 特徴2: 操作が直感的 特徴3:内容自体に集中できる環境 特徴4: 気軽にルールを記入できる 特徴5: プロトタイプツールと連携できる 特徴6: 挿入できるファイル形式が豊富 特徴7: Lottie Animationも入れられる 特徴8: 見出し、本文などの余白が適切、読みやすいデザイン 【ツール検証2: InVision DSM】 特徴1: デザインデータをアップロードしてそのままドキュメント化 特徴2: アセットの階層管理できる 特徴3: 無料版でも無制限でデザインガイドを作成できる 特徴4: InVisionのデザインファイルと一括管理 【InVision DSM とZeroheightの比較】 共通点 相違点 所感 【ツール検証3: Confluence】 特徴1: 文書の構造化しやすい、すらすら書ける 特徴2: Zeplinのリンクを埋め込む可能 所感 最後に: ツールは新しい可能性をもたらす 参考記事 【背景・問題】 以前デザインガイドライン制作について書いてみました。 tech.aptpod.co.jp しかし、デザインガイドラインを実際に運用し始めると、まだたくさんの問題があります。 「メニューで表示できる最大の文字数は?」 「ローディングするとき、ボタンの表現は?」 「同じようなフォームを3回くらい作成したけど、組み立ての基準ってなんだっけ」 「メイン画面の最小幅?最大幅?」 … このように、共通のコンポーネントを、画面上で組み合わせても、デザインの判断基準を確認できない状況です。 「デザインのルールが明文化していないこと」 が原因だと思います。 デザインガイドラインを管理するのに、 デザインコンポーネントをリストアップするだけでは足りませんでした 。   2つの方向が必要だとわかりました。 コンポーネンをまとめるデザインライブラリー ルールを明文化 、言語化するドキュメント 【デザインガイドラインをSketchでまとめてみた】 アプトポッドではSketchでデザインを制作し、Zeplin経由で開発チームに共有するワークフローになります。 まずは、ツールを増やしたくないため、Sketchでまとめてみました。 しかし、Sketchの画面では、確認したい要素を検索しにくいです。 デザインツール内でガイドラインをまとめようとすると、ドキュメントの体裁を考えながら作業することが大変です。 見出しをそろうことに時間がかかった 一番悲しいことは、共有、レビューしにくいので、結局誰も確認できないファイルになったことです。 Sketchの制約 デザインに特化したツールは、ドキュメントのまとめには適しないとわかりました。 (早く気づけばよかったのに…) 更新の非効率 デザイナー以外は更新できない 文書のコンテンツ制作に集中できない 記載項目の制限 動くコンポーネントを挿入できない 関連するコンポーネントにリンクできない サンプルコードを挿入できない 共有の非効率 レビューやコメントしにくい チームを跨いで共有しにくい、最終的に誰も確認しない 【Design Document Toolを導入したい】 Sketchでは今回の課題に満足できないため、別のツールについて調べてみました。 こちらの記事がとても参考になります。 hike.one デザイン業務で使用するツールで主に 「Design Tool」「Document Tool」「Design Document Tool」 3つがあるとわかりました。 1.Design Tool 主にデザイン画面を作成するツール。Sketch、InVision、Figma、UXPinなどが該当します。 2.Document Tool 主に設計文書などの情報を文字ベースでまとめるツール。Notion、Confluence、Githubなどが該当します。 3.Design Document Tool アクセス可能なWebサイトであり、デザイン画面やテキストを含む複数のページで、デザインを文書化できるツールです。Zeroheight、InVision DSM、UXPin、Frontifyなどが該当します。 以上から、デザインガイドラインをまとめるには「Design Document Tool」が適していると考えています。 Design Document Toolの選択基準 情報の安全性(SSO対応、信頼できる会社など)を調査した上で、利便性にフォーカスして、基準を仮に作りました。 作業の利便性から考えると: Sketchのライブラリーをできるだけそのまま流用できる コンテンツの執筆に集中できる どんな職種でもアクセスしやすい環境 バージョンの管理ができる デザイン要素のインタラクション、アニメーションを挿入できる ページのデザインが見やすい、ストレスを感じない 先の記事を参考にし、現時点で重視する要素にうまく対応してくれそうな Zeroheight や InVision DSM に絞りました。 現在アプトポッド社内で使っているDocument Toolの Confluence も試しました。 【ツール検証1: Zeroheight】 デザインシステムのドキュメント化に特化したツールです。 特徴1: デザインデータをアップロードしてそのままドキュメント化 Zeroheightに載せたいSketchファイルをプラグイン経由でアップロードするだけで連携ができます。 アップロードが完了すると下記のデザインデータが同期されます。 Symbol、Artboards、Layer Style、Document Colors、Text Styles ※補足: Sketch側でまずコンポーネントやスタイルを整理する必要があります 特徴2: 操作が直感的 項目をドラッグ&ドロップして並び替えできます。 特徴3:内容自体に集中できる環境 ルールの記入自体に集中できない理由は、以下の手間があることです。 ドキュメントの見た目のデザイン   各ページ共通の項目設計 Zeroheightはこの2つの悩みを解決してくれます。 読みやすいページのデザインを持ち、ドキュメントの見た目をデザインする手間が不要です。 さらに、Color、Text、Component各自の項目名をゼロベースでから設計しなくてもよいです。 Layout、Color、Component用のテンプレート 上記項目をひたすら埋めれば、見やすいドキュメントが大まかに完成できます。 要素によって追記するルールもありますが、ヒントを得ながら記入できます。 特徴4: 気軽にルールを記入できる Rulesのテンプレートを追加し、Do、Don’t、注意などのルールを追記、画像でもアップロードできます。 整理していく中で、今までのデザインガイドラインの振り返りもできます。 気軽にルールを追加できる、画像ベースでもOK 特徴5: プロトタイプツールと連携できる InVisionやFramerと連携できます。 今回はFramerと連携してみました。 Framer側で、コンポーネントを制作し、Embed リンクを発行します。その後Zeroheight側でリンクを貼ると、随時同期できるインタラクティブコンポーネントが反映できます。 Framer側で制作、リンクを発行 Zeroheight画面にリンクを貼る Framerのアートボードを修正し、Zeroheight側でRefreshボタンをタップして最新のデザインを反映します。 特徴6: 挿入できるファイル形式が豊富 Embed機能を使い、デザインや開発ツール以外のGoogle Driveなどの業務ツールのコンテンツも簡単に入れられます。 特徴7: Lottie Animationも入れられる 凝ったアニメーションを入れたい場合、Lottie Animationのiframe URLを貼るだけでも挿入できます。 特徴8: 見出し、本文などの余白が適切、読みやすいデザイン 文書の構造化がしやすく、見出しや本文の余白が適切で、長文を読んでもストレスを感じません。 【ツール検証2: InVision DSM】 InVisonが開発している共同チーム向けの設計システムプラットフォームです。 Sketch内のデザインライブラリーをアップロードし、色、Component登録などの流れは 「Zeroheight」とあまり変わりがないので、 簡単に紹介します。 InVision Craftアプリをダウンロード、Mac Tool Barで起動し、Sketch Pluginをダウンロードする必要があります。 Sketchのプラグイン 特徴1: デザインデータをアップロードしてそのままドキュメント化 既存のコンポーネントライブラリーをアップロードするとこのような画面になります。すでに「Foundations」「Components」などの画面が存在しています。 項目はZeroheightと比べると若干少ないです。 何パターンかテンプレートが用意されており、とても参考になります。 Component用のテンプレート Color用のテンプレート しかし、Zeroheightと比べると、デフォルトの項目が少ない分、ルールの策定には少し労力がかかりそうなイメージです。 ルールの策定に時間をかけたくない、もっとヒントが欲しい場合はZeroheightの方が良いかと感じます。 特徴2: アセットの階層管理できる 共通コンポーネントを管理しているSketchライブラリーをInVisionにアップロードすると、このような管理フォルダーが作成されます。 コンポーネント以外、アイコン、画像などの一括管理もできます。 特徴3: 無料版でも無制限でデザインガイドを作成できる Zeroheightの無料版は、デザインガイドが1つしか作成できませんが、InVisionの無料版でも無制限で作成できます。 特徴4: InVisionのデザインファイルと一括管理 既にInVisionを使っているのであれば、デザイン、レビュー、共有、デザインシステム管理を一つのツールで完結できます。 【InVision DSM とZeroheightの比較】 共通点 カスタムドキュメントを使用して、チームを同じページに配置できる デザインガイドライン、コンポーネント一覧、アクセシビリティ標準など、一括で管理できる場所 Sketchのライブラリーをそのままアップして使える Color Text Style Component 開発者のツールとも連携できる Storybookの統合により、本番環境に対応したコードを直接埋め込んでリンクできる 相違点 大項目 小項目 InVision DSM Zeroheight ページ Componentページ Component詳細   Colorページ 機能 ページの複製可能か 不可 可能   デザインのDo、Don’tの比較が可能か 不可 可能 外部ツールと連携 開発ツールとの連携 【無料版】 ・Storybook ・code component library 【企業版】 ・Styleguidist ・CodePen ・CodeSandbox ・Pattern Lab 【無料版】 ・Storybook integration ・code component library (HTML/CSS/JS) ・Code snippets 直接記入 ・CodePen デザインツールとの連携 少ない ・InVision ・Sketch 多い ・Sketch ・Figma ・AdobeXD ・InVision ・Framer 動くUI Componentに動きを入れる 開発ツールと連携 ・Framer ・Lottie animation 管理 バージョン管理 Enterpriseプランのみ可能 Enterpriseプランのみ可能 ユーザー権限設置 Enterpriseプランのみ可能 Enterpriseプランのみ可能 【Free】 ・無料 デザインガイドの数無制限 ・機能の制限 【Enterprise】 ・個別相談 ・大体$35ユーザー1人(編集者、閲覧者とわず) 【Free】 ・無料 ・編集者1人、デザインガイド1つ ・機能無制限 【Starter】 ・1人の編集者$49/月、編集者5人まで ・小さいなプロジェクトチームに適する 【Professional】 ・個別相談 ・編集者5~10人(割引あり) ・中小企業の開発チーム 【Enterprise】 ・個別相談、10人以上編集者、デザインガイド10つまで ・プラットフォームを提供している企業に適する 【Agency】 ・複数の顧客のためのデザインガイドを管理 ・デザインエージェンシーに適する 編集者5人以上使用すると、月$245以上かかります。 まとめ 良いところ ・ソースが表示される ・Componentの名前はSketchと一致 、スラッシュの分割に影響されない。 例えば「 Button/Primary/32h」は名前そのままで、検索しやすい ・InVison内の他の機能も使える ・Componentごとにdescriptionを追加できる ・要素がはっきり表示できる ・ページの複製ができる ・無料版で操作できることが多い 微妙なところ ・表示がぼやける ・Component詳細画面でサイズ、余白を確認できない ・ページの複製ができない ・機能、テンプレートが多いため、どの表現を選択するか迷う可能性がある ・無料版で1つのデザインガイドしかできない 所感 操作の流れについて、InVison DSMとZeroheightは似ています。 残りの比較ポイントは、細かい操作感、デザインの好み、現状使用しているツールかと思います。 もしチーム内でのデザインツールはInVisionを採用しているのであれば、InVision DSMは一番良いでしょう。 開発やデザイン以外のメンバーも巻き込みたい、他のデザインツールを使用している場合、または少ない労力でリッチなデザインガイドを作りたい場合、個人的にはZeroheightが一番おすすめです。 また、社内で本格で運用していくと安全性を確保するために有料版がおすすめです。 【ツール検証3: Confluence】 アプトポッド社内では、情報共有ツールとしてConfluenceを利用しています。 新しいツールを導入してもいいですが、なるべくツールを増やしたくない、ミニマムから動き出す考えで、Confluenceで簡単にデザインガイドラインをまとめてみます。 特徴1: 文書の構造化しやすい、すらすら書ける Cofluenceの文章テンプレートが豊富なので活用できるものが多いです。 特徴2: Zeplinのリンクを埋め込む可能 ConfluenceはZeplinのリンクを埋め込んで試してみました。 良いところは、Zeplinに更新を行うと、Confluence側も同期できることです。 微妙なところは、余白のサイズが確認できないことや、操作が重いことです。 また、コンポーネントライブラリーをそのまま反映できず、共有専用のまとめファイルが必要です。 所感 Confluenceの良いところは 全職種アクセスできる、編集できる 文書をすらすらかける 微妙なところは デザインツールとの適切な統合がない Sketchのライブラリーをそのままアップロードして反映できない Zeplinを埋め込む機能がまだ使いにくい 最初は、内容を企画する段階では、早いスピードでレビューを回す必要があります。 Confluenceの機能や弱点を踏めると、まずはConluenceで画像を貼って、文書のコンテンツ制作に集中します。 ある程度でコンテンツができたら、Zeroheightに登録してもいいと思います。 デザインガイドラインの明文化の第一歩を踏み出すには、まずは導入ありきでなく、社内Document Toolから始めてみるといったことがよいと感じました。 最後に: ツールは新しい可能性をもたらす 最後までご覧くださってありがとうございます! 正直、普段業務内で使っている「Sketch → Zeplin」の流れに関して、すでに当たり前になってしまいました。 しかし、道具の選択ひとつで、全体のワークフローや組織の変化に対して新しい可能性をもたらすことをわかりました。 検証結果以外、私には以下のような発見がありました。 組織がツールの導入するときに考慮するポイント ツールの検証は組織のワークフローや志向に影響をもたらす 今デザインしているツールも、お客さんのワークフローに何かの変化をもたらす 価格、安全性、開発チームの作業コストなどの考慮もあり、最終的にどんなツールの選択になるかはまだ先ですが、一歩進めたかと思います。 これからは、今回の調査や思考のベースで社内のワークフロー改善につなげていきます! 参考記事 uga-box.hatenablog.com www.designsystems.io dev.classmethod.jp goworkship.com
みなさまこんにちは。プロダクト開発本部の岸田です。 以前に「ハードウェア側に機械学習環境を立てて推論を行い、クラウドに結果を収集して分析状況を確認する」ユースケースを こちら の記事で考察しました。手軽にローカルデバイスとクラウドを連携するサービスとして有名なものが「AWS IoT Greengrass」ですよね。公式のドキュメントでもエッジ推論システムをAWS IoT Greengrassで構築するユースケースが紹介されています。 docs.aws.amazon.com そこで上記を参考に、弊社の製品であるJetson TX2搭載のEDGEPLANT T1を利用して、AWSサービスを活用したエッジ推論システムをつくってみたいと思います! この記事の対象読者 EDGEPLANT T1 で手軽にエッジ推論を構築するメリット 今回のゴール 構成イメージ 実際に構築してみる モデルをAmazon SageMakerを用いてコンパイル EDGEPLANT T1で機械学習環境を構築する EDGEPLANT T1にAWS IoT Greengrassをインストールする 推論を行うコードをLambdaとしてデプロイする LambdaをGreengrass Groupに追加する SageMakerでコンパイルしたモデルをGreengrass Groupに追加する 推論実行用のローカルデバイスをGreengrass Groupに追加する サブスクリプションを定義する デプロイパッケージをテストする IoT Analytics にデータセットをためる QuickSightでダッシュボードを作り結果を確認する まとめ この記事の対象読者 NVIDIA提供のJetsonシリーズの機器上で、上記の公式ドキュメントのようなことがやりたい方 Jetson TX2を扱う場合は、JetPackの依存関係などを考慮しながら適切に進める必要があり、いくつか考慮すべきポイントがあります。本記事ではその点をいくつかまとめてます。 EDGEPLANT T1を単体で購入いただいている方 EDGEPLANT T1をつかって簡単な機械学習推論システムを構築したい... そのような方向けにサンプルとしてご参考頂ける内容をまとめています。(コンセプトの詳細は次項をご参照ください。) EDGEPLANT T1 で手軽にエッジ推論を構築するメリット (こちらはEDGEPLANT T1の宣伝も兼ねてるので、不要な方は読み飛ばしてください。) 弊社ではEDGEPLANTというハードウェアブランドを提供しており、その中でも機械学習の実行環境として利用できるハードウェアが EDGEPLANT T1 になります。 弊社が提供しているintdashと組み合わせることで、 EDGEPLANT T1で機械学習モデルを動かし、推論結果をintdashサーバーに時系列化された状態で送付することで、専用のWebアプリケーションでリアルタイムにデータを可視化したり、後から数種類のデータが時刻同期された状態でデータを確認することができます。 intdashの概要 このしくみは、既にリリース発表されている大阪ガス様とのプロジェクトでも利用されています。 www.aptpod.co.jp 一方で、EDGEPLANT T1は単体でも購入することができます。しかしながらご購入いただいた方々からは「どうやってつかえばいいの?」、「システムとして組み込むにはどうすればよいの?」など、ざっくりシステムとして作りたいイメージはあるものの、具体的な実現手段がわからない…. こういう声を頂いておりました。 そこで今回は予算の都合などで「ユースケースを試して試験運用してみたいが、intdashとの連携まではなかなか手が出せない」方々向けに、皆様に馴染みの深いAWS様のサービスを存分に利用して、エッジ推論システムを手軽に構築するサンプルをご紹介いたします。 まずはEDGEPLANT T1で単純にモデルを動かしたい!という方は、 こちら の記事をご参考ください。 今回のゴール 今回は、「ハードウェア側に機械学習環境を立てて推論を行い、クラウドに結果を収集して分析状況を確認する」システムを一人で構築したいと思います。 流れとしては 模擬クライアントから、画像を指定したメッセージを送る メッセージを待機しているEDGEPLANT T1がメッセージを受け取り、指定された画像に対して推論する 推論結果をサーバーに送る 1〜3を繰り返し、溜まったデータを分析ツールで可視化する というイメージです。 EDGEPLANT T1にはMXnetベースでトレーニングされたResNetというイメージ分類モデルをデプロイし、事前にEDGEPLANT T1に画像を数枚配置し、メッセージで指定された画像に対して何が写っているか推論してもらいます。ユーザーは遠隔で「推論結果の確信度」や「パフォーマンスの推移」を観察すると想定します。 やりとりのイメージ 最終的にはエッジ側の推論の様子を、以下のようなダッシュボードで確認できることを想定しています。 可視化イメージ 私が実施した際は各所でつまづき気が遠くなったときもありましたが、約3日程度で一通り構築できました。費用もデータ量に依存しますが、1000円かからないくらいの料金で試すことができました。 構成イメージ AWS IoT Greengrassを利用したケースによくあるアーキテクチャに近いですが、以下のような構成を検討しました。 構成イメージ モデルをEDGEPLANT T1で動作するように、Amazon SageMaker Neoを利用してコンパイルします EDGEPLANT T1で、モデルが動くように機械学習環境を構築します AWS IoT Greengrassを用いて以下のアセットをパッケージ化しデプロイします Lambda関数として作成された推論コード Amazon SageMaker Neoでコンパイルしたモデル デバイスとサーバー間のメッセージは、AWS IoT Coreを経由しMQTTメッセージでやりとりします EDGEPLANT T1からパブリッシュされたデータはAWS IoT Analyticsにて蓄積し、Amazon QuickSightでデータを可視化します 実際に構築してみる それでは、実際に構築してみましょう。 モデルをAmazon SageMakerを用いてコンパイル 今回はMXnetのGluonCVから ResNetのモデルをダウンロードします。 from gluoncv import model_zoo import mxnet as mx model_name= 'ResNet50_v2' net = model_zoo.get_model(model_name, pretrained= True ) net.hybridize() dummy = mx.nd.ones([ 1 , 3 , 224 , 224 ]) _ = net(dummy) net.export(model_name, epoch= 0 ) ダウンロードしたモデルは、S3に格納しておきます。 import boto3 import tarfile session = boto3.session.Session() # compress packname = 'model.tar.gz' tar = tarfile.open(packname, 'w:gz' ) tar.add( '{}-symbol.json' .format(model_name)) tar.add( '{}-0000.params' .format(model_name)) tar.close() # send to s3 s3 = session.client( 's3' ) bucket = 'sample-bucket' s3key = 'resnet-model/resnet-model' s3.upload_file(packname, bucket, s3key + '/' + packname) 次に、SageMaker Neoにてモデルを格納したS3のパスを指定してジョブを実行します。 # Replace with the role ARN you created for SageMaker sagemaker_role_arn = "arn:aws:iam::XXX:role/service-role/XXX" framework = 'mxnet' target_device = 'jetson_tx2' data_shape = '{"data":[1,3,224,224]}' # Specify the path where your model is stored s3_model_uri = 's3://{}/{}/{}' .format(bucket, s3key, packname) # Store compiled model in S3 within the 'compiled-models' directory compilation_output_dir = 'resnet-model/compiled-models/test' s3_output_location = 's3://{}/{}/' .format(bucket, compilation_output_dir) # Give your compilation job a name compilation_job_name = 'resnet-model5' sagemaker_client.create_compilation_job(CompilationJobName=compilation_job_name, RoleArn=sagemaker_role_arn, InputConfig={ 'S3Uri' : s3_model_uri, 'DataInputConfig' : data_shape, 'Framework' : framework.upper()}, OutputConfig={ 'S3OutputLocation' : s3_output_location, 'TargetDevice' : target_device}, StoppingCondition={ 'MaxRuntimeInSeconds' : 900 }) 上記を実行するとコンパイルジョブが発生し、問題なければ完了します。コンソール上でもジョブのステータスを確認することができます。 コンパイルジョブの確認画面 これで、コンパイルは完了しました。 EDGEPLANT T1で機械学習環境を構築する T1でモデルが動くように、機械学習環境を構築していきましょう。このステップでは、 AWSのドキュメント にもある通り Neo Deep Learning Runtime (DLR) をインストールします。 通常であれば pip install で DLRをインストールすれば完了しますが、EDGEPLANT T1ではJetson TX2を採用しており、JetPackのバージョンやOSのアーキテクチャと一致するDLRを用意する必要があります。そのため、T1上でDLRをビルドして用意します。 DLRの公式ページ を参考に、ビルドします。 cmakeのビルド&インストール sudo apt-get install libssl-dev wget https://github.com/Kitware/CMake/releases/download/v3. 17 . 2 /cmake-3. 17 . 2 .tar.gz tar xvf cmake-3. 17 . 2 .tar.gz cd cmake-3. 17 . 2 ./bootstrap make -j 4 sudo make install DLRのビルド&インストール git clone --recursive https://github.com/neo-ai/neo-ai-dlr cd neo-ai-dlr mkdir build cd build cmake .. -DUSE_CUDA = ON -DUSE_CUDNN = ON -DUSE_TENSORRT = ON make -j 4 cd ../python python3 setup.py install --user インストールし終えたら、実際にpython上でimport できるか試してみます。 root@aptuser-desktop:/greengrass/ggc# python Python 3.8.6 (default, Jan 24 2022, 21:19:25) [GCC 7.5.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import dlr CALL HOME FEATURE ENABLED You acknowledge and agree that DLR collects the following metrics to help improve its performance. By default, Amazon will collect and store the following information from your device: (省略) >>> DLRの初期ロード時の注意書きが出力され、無事importができました。 ⚠️ 注意 私が確認したときはAWS Lambdaの制約によりアーキテクチャがarm64の場合、 Python3.8 以上でないと対応しておりませんでした。バージョン3.8未満を利用されている方は Pythonのバージョンをアップデートすることをおすすめします。 EDGEPLANT T1にAWS IoT Greengrassをインストールする AWS IoT Greengrassに関連した手順は、基本的に以下手順に沿って実施しています。 docs.aws.amazon.com まず最初にEDGEPLANT T1に AWS IoT Greengrassをインストールします。初期手順については こちら の手順のModule1〜Module2を参考にしました。 Module1は、Greengrassがアクセスする用のユーザーを設定するパートです。私は以下手順を実施しました。 sudo adduser --system ggc_user sudo addgroup --system ggc_group 上記のドキュメントでは openjdk-8-jdk をインストールする手順がありますが、今回はGreengrass Managerは利用しないのでスキップしました。 Module2はAWS IoT Greengrassの画面上でデバイス情報を新規作成し、作成されたクライアント認証情報をダウンロードしてセットアップします。 最終的にEDGEPLANT T1上で以下の動作が確認できたら完了です。 root@aptuser-desktop:/greengrass/ggc# core/greengrassd start Setting up greengrass daemon Validating hardlink/softlink protection Waiting for up to 1m10s for Daemon to start Greengrass successfully started with PID: 28776 推論を行うコードをLambdaとしてデプロイする 実際にモデルの推論を行うソースコードをAWS Lambdaにデプロイします。 まず、AWS IoT Greengrass経由でSubscribeしたメッセージをトリガーに、モデルを動作させて推論した結果をPublishするコードをAWS Lambda上にデプロイします。 サンプルとして、以下のコードを用意しました。こちらも 公式サンプル をほぼ参照しております。 (一部前処理など公開の都合上省略しています) import logging import os from dlr import DLRModel from PIL import Image import greengrasssdk import numpy as np import json import time # Initialize logger customer_logger = logging.getLogger(__name__) # Create MQTT client mqtt_client = greengrasssdk.client( 'iot-data' ) # Initialize model_resource_path = os.environ.get( 'MODEL_PATH' , '/ml-model' ) print (model_resource_path) dlr_model = DLRModel(model_resource_path, 'gpu' , 0 ) # Load Synset synset_path = os.path.join( '' , '/home/aptuser/develop/t1-ml-workflow/synset.txt' ) with open (synset_path, 'r' ) as f: synset = eval (f.read()) def softmax (x): e_x = np.exp(x) sum_x = np.sum(e_x) return e_x/sum_x def predict (image_path): """ Predict image with DLR. The result will be published to MQTT topic '/resnet/predictions'. :param image: numpy array of the Image inference with. """ print ( 'start prediction process.......' ) im = Image.open(f '/home/aptuser/develop/t1-ml-workflow/{image_path}' ) im = np.asarray(im.resize(( 224 , 224 ))) im = im.astype(np.float32) if len (im.shape) == 2 : # for greyscale image im = np.expand_dims(im, axis= 2 ) input_data = { 'data' : im} print ( 'start prediction.' ) load_start_time = time.time() out = dlr_model.run(input_data) finished_time = time.time() print ( 'finished.' ) top1 = np.argmax(out[ 0 ]) prob = np.max(softmax(out[ 0 ])) prediction_time = finished_time - load_start_time exe_time = str (datetime.datetime.now()) result = { 'class' : synset[top1], 'probability' : float (prob), 'prediction_time' : finished_time - load_start_time, 'time' : exe_time } # Send result send_mqtt_message(result) def send_mqtt_message (message): """ Publish message to the MQTT topic: '/resnet-50/predictions'. :param message: message to publish """ mqtt_client.publish(topic= '/resnet/predictions' , payload=json.dumps(message)) # The lambda to be invoked in Greengrass def lambda_handler (event, context): print (f 'subscribed message: {event}' ) try : predict(event[ 'message' ]) except Exception as e: customer_logger.exception(e) send_mqtt_message( 'Exception occurred during prediction. Please check logs for troubleshooting: /greengrass/ggc/var/log.' ) Lambda関数を作成し、上記のコードをデプロイします。 Lambda関数の作成 このとき、JetsonTX2の場合は arm64をアーキテクチャとして指定する必要がありますが、残念なことにPython 3.7以下のバージョンは利用できませんでした。そのためPython3.8以上を利用します。 バージョンを発行し、エイリアスに紐付けて作成します。 Lambda関数のエイリアス詳細画面 これで、Lambda関数の準備も完了です。 LambdaをGreengrass Groupに追加する 先程作成したLambda関数をGreengrassのグループに追加します。 詳細の手順は Step 4: Add the Lambda function to the Greengrass group を参考にしてください。 SageMakerでコンパイルしたモデルをGreengrass Groupに追加する 先程作成したSageMaker Neoでコンパイルしたモデルをグループに追加します。 詳細の手順は Step 5: Add a SageMaker Neo-optimized model resource to the Greengrass group を参考にしてください。 推論実行用のローカルデバイスをGreengrass Groupに追加する 今回のGreengrassの設定で、Lambda関数をコンテナ形式で動作させる設定にしたため、GPUにアクセスできるように設定する必要があります。章の冒頭で触れている こちら のドキュメントの末尾にある”Configuring an NVIDIA Jetson TX2”に、設定すべきデバイスとボリュームのパスが書かれています。 基本的な手順は、 こちら の手順と同じです。 Greengrassの"グループ"から"リソース" を選択し、"ローカルリソースの追加"を押下します。リソースの作成画面が開くので、各デバイスのパスを入力します。 ”Configuring an NVIDIA Jetson TX2”に記載されているDevice Pathを指定してください。 name path nvhost-ctrl-gpu /dev/nvhost-ctrl-gpu nvhost-dbg-gpu /dev/nvhost-dbg-gpu nvhost-ctrl /dev/nvhost-ctrl nvmap /dev/nvmap nvhost-prof-gpu /dev/nvhost-prof-gpu ちなみに、上記を追加せずGreengrassを起動すると、Lambda関数側で以下のエラーが表示されます。 [2022-01-25T00:13:01.06+09:00][ERROR]---------------------------------------------------------------- [2022-01-25T00:13:01.06+09:00][ERROR]-An error occurred during the execution of TVM. [2022-01-25T00:13:01.06+09:00][ERROR]-For more information, please see: https://tvm.apache.org/docs/errors.html [2022-01-25T00:13:01.06+09:00][ERROR]---------------------------------------------------------------- [2022-01-25T00:13:01.06+09:00][ERROR]- Check failed: (e == cudaSuccess || e == cudaErrorCudartUnloading) is false: CUDA: no CUDA-capable device is detected 同様に、ボリュームも”Configuring an NVIDIA Jetson TX2”に記載されているパスを指定します。 他にもLambda関数側でアクセスしているボリュームがあれば、同じように指定します。 name path shm /dev/shm tmp /tmp ※ 今回はサンプルなのでセキュリティを考慮していませんが、 実際に利用する場合はGreengrassのアクセス許可がセキュリティ的に問題ないか、 しっかり検討した上で指定のリソースを参照してください。 サブスクリプションを定義する 先程のパート「推論を行うコードをLambda関数としてデプロイする」にて、プッシュされたメッセージを参照に推論を実施し、AWS IoTにPublishするコードを書いていたと思います。ここでは、そのAWS IoTとLambda関数のサブスクリプションを定義します。 今回も、 こちら の手順を実行しました。 サブスクリプションの定義 トピック 説明 /resnet/predictions IoT Cloud上のMQTTクライントからプッシュされるメッセージをLambda関数でサブスクライブするトピック /resnet/test Lambda関数からプッシュされるメッセージをサブスクライブするトピック これで、デプロイパッケージができました!! それでは、早速デプロイしてみましょう。デプロイの手順は こちら を実施します。 上記手順後、デバイス側での動作状況をCloudWatchで確認します。 CloudWatchのログ [2022-01-25T15:31:21.811+09:00][INFO]-lambda_runtime.py:149,Running [arn:aws:lambda:ap-northeast-1:XXXXXXXXX:function:ml-edgeplant-test:6] [2022-01-25T15:31:21.811+09:00][INFO]-ipc_client.py:210,Getting work for function [arn:aws:lambda:ap-northeast-1:XXXXXXXXX:function:ml-edgeplant-test:6] from http://localhost:8000/2016-11-01/functions/arn:aws:lambda:ap-northeast-1:XXXXXXXXX:function:ml-edgeplant-test:6/work 上記のログが出力され待機状態になっていればOKです! デプロイパッケージをテストする パッケージのデプロイが完了したので、実際に各サブスクリプションが正常に定義されているか試してみます。 こちら の手順と同様に、AWS IoTのテストクライアントでメッセージを送ってみましょう。 今回は、テストクライアント側でサブスクライブするトピックを /resnet/predictions に指定します。 トピックのサブスクリプションの作成 指定後、トピックに紐付いたサブスクライブの項目が出てくるので、そちらでトピックを /resnet/test と指定し発行します。今回は goldfish の画像を指定します。 発行するトピックとメッセージの指定 すると、モデルの出力結果が返ってきました! goldfish と期待通りの推論結果が返ってきていることが確認できます。 送付結果 EDGEPLANT T1側の設定はこれで完了です。 IoT Analytics にデータセットをためる ようやくEDGEPLANT T1からAWS IoTにデータを送れるところまでできたので、次はそのデータをIoT Analyticsに投下しデータセットとして溜めていきます。 こちら の手順に従い設定を行います。 チャネルの作成 チャネルの作成では、先程指定した /resnet/predictions を指定します。 データセットの作成 データセットの作成では、定期的にデータセットに溜めたいデータを出力できるクエリを定義します。今回はすべての項目をそのまま参照したいので、すべてのデータをクエリするようにしました。 SELECT * FROM edgeplant_project_datastore スケジュールは、1分間に一回実行するようにします。 パイプラインの作成 パイプラインでは、Lambda関数が送付しているJSONの各項目とデータ型を指定します。 上記の手順が終了したらテストしてみましょう。 先程のパート「デプロイパッケージをテストする」で実施した手順を複数回実施して、AWS IoT側が受け取ったメッセージがデータストアに蓄積されているか確認します。 メッセージごとに、指定の画像を切り替えながら複数回トピックを発行します。 連続してトピックを発行 AWS IoT Analyticsに戻り、データセット > コンテンツ をクリックすると、コンテンツが生成されていることが確認できます。 データセットのコンテンツ このコンテンツをクリックし、実際にデータが格納されている様子を確認します。 実際のデータ 無事、データが蓄積している様子を確認することができました。 QuickSightでダッシュボードを作り結果を確認する いよいよ最後のパートになります。QuickSightを使って、ダッシュボードを作ります。 まずデータセットを作成します。"データセット"の項目をクリックし、"新しいデータセット"をクリックします。すると、参照先のサービスの一覧がでてきます。今回は AWS IoT Analyticsを参照します。 参照先の選択画面 すると、既に作成したデータセットが表示されるので、指定のデータセットを選択して “データソースを作成“をクリックします “分析“で “新しい分析“をクリックし、対象のデータセットを参照します データセット選択画面 自分が可視化したいデータに合わせて、パーツを選びダッシュボードを構築します。 作成イメージ 最終的には、以下のようなダッシュボードが作成できます。自分が評価したい数値をベースに、オリジナルのダッシュボードを構築してみましょう。 可視化イメージ ダッシュボードが完成したら、全作業が完了です。 お疲れ様でした! まとめ 今回は、EDGEPLANT T1とAWSのサービスを活用することで、手軽にエッジ推論システムが構築できました。このサンプルを利用することで、以下のようなケースに応用することができると思います。 自分の希望するモデルに差し替えて、すぐにシステムにデプロイできる 台数を増やしたいときは、AWS IoT Greengrassでデプロイするだけでシステムに組み込める EDGEPLANT T1をシステムとして活用したいとなった場合、EDGEPLANT T1でモデルを動かすだけではなく、その推論結果をクラウドに蓄積し、分析や再学習に応用したいケースに発展することが多いです。しかしながら、そのシステムを構築するのにも費用や時間がかかってしまう… そのようなときに、今回のサンプルを利用することができます。 ちなみに以下のような要望がある場合は今回のシステムでは実現が難しいので、ぜひ弊社のintdashの活用をご検討ください。 リアルタイムで推論状況を確認したい AWS IoT AnalyticsおよびAWS Quick Sightは、基本的にバッチ処理でデータを処理するため、リアルタイムで推論状況を確認することができません。intdashでは、高粒度データのリアルタイム送受信を可能にし、再生に特化した高機能ダッシュボードをご活用いただけます。 他のセンサーデータを同期させながらデータを収集・管理したい 機械学習モデル以外に、走行中の動画データや、CANなどの他データと同期しながらデータを収集・管理したい場合、このしくみでは達成できません。intdashでは、複数に横断した高粒度データを、ミリ秒単位で同期させデータを収集し、あとから管理することができます。 intdashを検討する前の手軽な機械学習システムを構築する、という位置づけで、ぜひご参考いただけると嬉しいです。 それでは、最後までお付き合いいただきありがとうございました!
はじめまして。 SRE チームの細谷です。 弊社製品 intdash のサーバーサイドでは、 intdash Server と呼ばれるミドルウェアが動作しており、intdash の多様なデータパイプラインの構築を実現しています。 intdash Server の構成要素の 1 つに時系列データベースがあり、弊社では InfluxDB OSS を採用しております。 InfluxDB OSS は、v1( 1.X )と v2( 2.X )のバージョンがリリースされており、現在運用している intdash では v1 を利用しています。 SRE チームでは v2 への移行を検討しており、v1 との比較などを含め検証を進めています。今回は InfluxDB v2 の導入や移行などをご紹介いたします。 導入手順 インストール セットアップ v1 との互換性について v1 からの移行手順 事前準備 時系列データのマイグレーション クエリ言語について Flux 言語によるクエリパフォーマンスの向上 さいごに 導入手順 今回は Amazon Linux 2 の EC2 インスタンス上にインストールします。インストールするバージョンは以下になります。 influxdb: 2.1.1 influx-cli: 2.2.1 インストール influxdb をインストールします。systemd で起動するため、rpm パッケージ経由でインストールします。 wget https://dl.influxdata.com/influxdb/releases/influxdb2-2.1.1-x86_64.rpm sudo yum localinstall influxdb2-2.1.1-x86_64.rpm 起動前に config を設定します。 /etc/influxdb/config.toml 1 に記述するか、環境変数を介して渡すことができます。詳細オプションは、 公式ドキュメント をご覧ください。 設定が完了したら、influxdb を起動します。 sudo service influxdb start 続いて CLI でのセットアップのため、 influx-cli をインストールします。 wget https://dl.influxdata.com/influxdb/releases/influxdb2-client-2.2.1-linux-amd64.tar.gz tar xvzf influxdb2-client-2.2.1-linux-amd64.tar.gz sudo cp influxdb2-client-2.2.1-linux-amd64/influx /usr/bin/ セットアップ デフォルトの User や Bucket 2 、Org を作成します。 influx setup -f \ --name default \ # 初期設定の名前 --username influx \ # 初期ユーザー名 --password influxpassword \ # 初期パスワード --token influxtoken \ # InfluxDB へ接続する API Token --org influx \ # 組織名 --bucket intdash \ # 初期バケット --retention 0 \ # Retention Policy --shard-group-duration 168h0m0s # Shard Group Duration ここで、 --token オプションより influxdb に接続するための API Token 値を指定することができます(指定しない場合はランダムな値になります)。 設定を完了すると、以下のコマンドよりデフォルトの config 3 が作成されていることが確認できます。 $ influx config ls Active Name URL Org * default http://localhost:8086 aptpod また、デフォルトの Bucket も作成されていることが確認できます。 $ influx bucket ls ID Name Retention Shard group duration Organization ID Schema Type 14aed3a5dcc5af35 _monitoring 168h0m0s 24h0m0s 06adc337029ceb21 implicit 58a8e876e4cf0bbf _tasks 72h0m0s 24h0m0s 06adc337029ceb21 implicit 61d8afb4a9e0413c influx infinite 168h0m0s 06adc337029ceb21 implicit _monitoring 、 _tasks は System Bucket と呼ばれ、1 インスタンス毎にデフォルトで作成されます。 その他、必要な Bucket は以下のコマンドで作成できます。 influx bucket create \ --name influxdb2 \ # バケット名 --org influxdb \ # 組織名 --retention 0 \ # Retention Policy --shard-group-duration 168h0m0s # Shard Group Duration v1 との互換性について v2 では、主要言語に クライアントライブラリ が提供されていますが、v1 クライアントライブラリとの互換性もあります。InfluxDB 側に v1 auth と v1 dbrp を作成することで、v1 クライアントライブラリを使用できます。 上記は influx-cli コマンドで作成可能です。(事前に必要となる Bucket を作成し、ID を控えておく必要があります。) v1 auth InfluxDB にBasic 認証で接続できる認証設定をします。 --read-bucket 、 --write-bucket オプションにて、read/write 可能な Bucket を指定します。 influx v1 auth create \ --username <ユーザー名> \ --password <パスワード> \ --org <組織名> \ --read-bucket <Bucket ID> \ --write-bucket <Bucket ID> v1 dbrp Database と Retention Policy を、Bucket にマッピングする設定をします。 influx v1 dbrp create \ --bucket-id <Bucket ID> \ --db <DB 名> \ --rp <Retention Policy> 以上で v1 クライアントライブラリを使用可能な状態になります。 v1 からの移行手順 移行手順は「Automatically upgrade」と「Manually upgrade」の 2 通りがありますが、「Manually upgrade」で移行する手順をご紹介いたします。 こちらは時系列データを line protocol ファイルで書き出してマイグレーションするといった内容になっており、初期セットアップした InfluxDB v2 への移行を想定しています。 事前準備 移行前の準備として、 /etc/influxdb/config.toml を作成します。 v1 で設定していた config を元に、v2 の config を作成します。詳しいマッピングは 公式ドキュメント をご確認ください 4 。 時系列データのマイグレーション InfluxDB v1 サーバー上で、各 DB ごとに以下のコマンドを実行します。 line protocol ファイル形式で時系列データをエクスポートしています。 influx_inspect export \ -datadir /var/lib/influxdb/data \ # datadir を指定 -waldir /var/lib/influxdb/wal \ # waldir を指定 -database influxdb \ # DB 名 -retention autogen \ # Retention Policy -out /var/tmp/influxdb.lp \ # line protocol ファイルを出力するパス -lponly 次に InfluxDB v1 をアンインストールして InfluxDB v2 をインストールします。 5 その後、事前準備にて作成した /etc/influxdb/config.toml を配置して、上述した初期セットアップと Bucket 作成、v1 互換性の設定を実施します。 最後に、以下のコマンドから時系列データの書き込みを実行してマイグレーションは完了になります。 influx write \ --bucket influxdb \ # バケット名 --file /var/tmp/influxdb.lp # line protocol ファイルパス クエリ言語について v1 では クエリ言語として influxql が使用されていましたが、v2 では flux が使用されます。 (flux 言語自体は、時系列データベースだけでなく RDB や CSV などを扱うことができます。) influxql も API から使用可能ですが、上述した v1 dbrp 設定をする必要があり サポートされているクエリ が限られています。 また、Continuous queries は tasks という名前に置き換わり、flux での作成が必要です。詳細は 公式ドキュメント をご覧ください。 Flux 言語によるクエリパフォーマンスの向上 Flux 言語では、 Pushdowns という関数がサポートされています。 Pushdown とは 1 つ以上から成る関数であり、その関数はデータ操作をメモリ上で行わずデータソース上で実行します。 こちらを活用することで、CPU やメモリ使用率などの向上が期待できます。 実際に検証中のベンチマークテストにて InfluxQL と Flux の比較を実施しました。 参照するデータ量が大きいクエリを実行したところ、InfluxQL は OOM によって処理が止まってしまいましたが、Flux は正常に動作することが確認できました。 以下がメモリ使用率のグラフになります。 InfluxQL 実行時のメモリ使用率 Flux 実行時のメモリ使用率 顕著に差が出たのは、Pushdowns によるものと思われます。 長期間のデータ参照が必要なユースケースなどにおいて、Flux が有効な選択肢の 1 つとなることに期待できそうです。 さいごに InfluxDB のインストールや構成についてご紹介いたしました。 今回は検証向けの手順などがベースの内容となっておりますが、SRE チームでは運用環境に対する移行環境の整備など検証を進めています。 今後も引き続き進展あれば InfluxDB についてご紹介していきたいと思います。 json, yaml 形式でも記述可能です。 ↩ v2 では Database が Bucket として置き換わり、各 Bucket に Retention Policy / Shard Group Duration が設定されます。 ↩ ここでは Client 側( influx-cli )の config を指しています。検証では同一インスタンスに CLI をインストールしましたが、別インスタンスからもセットアップなどができます。 ↩ v1 と v2 の config option は大幅に変更されており、v2 から使用不可のものもあります。 ↩ 紹介した手順では、同一インスタンスを v2 に移行する内容を記載しています。運用中でダウンタイムなどを考慮して移行する場合は、 Dual write などを活用することをお勧めします。 ↩
Aptpod Advent Calendar 2021 24日目の記事です。 (土日休みにしているので最終日です) CTOの梶田です。 今年もあっという間でAdvent Calendarなんとか走りきれそうです。始まる前は、集まりがあまりよくなく焦りましたが、ロビー活動!?とかとかでなんとかなりました💦 これもそのひとつ。。。 tech.aptpod.co.jp Techブログでの挑戦も3年となり、今年も去年と同様に健全に(!?)土日は基本抜く形で投稿できました。慣れてきた部分もありつつ、なかなか続けるのは大変だなと痛感しています😓 (みんな忙しい中、頑張った!💪) というわけで! 早いもので2021年も終わろうとしています。毎年言ってますねw 年末ネタとして(勝手に)定着しつつある!?ので2021年も 昨年 と同様に振り返ろうと思います。 ちなみに メルペイ VPoE の @hidekさんも 今年も Merpay Advent Calendar を始めるにあたって、1年を振り返ってみたいと思います。 ちなみに、このネタで書くのは2019年、2020年に続き、三年連続になるわけですが、 このメソッドは大変楽なので、毎年ネタで困っている CTO/VPoE の方々に改めてオススメします。 とのことでした。そう思います!(同じく三年連続振り返りネタ💦) engineering.mercari.com はじめに 組織 ツール 技術ピックアップ 個人的に 2022年に向けて はじめに 2021年もCOVID-19は終息することなく、引き続き猛威を振るっており、リモートワークも定着してきた感はあります。最近は少し落ち着いていますが、まだまだどうなるか。。。 昨年 来年早々からいろいろリリースネタもありますし と書いた通り、 今年大きなトピックがいくつかありました。 シリーズCの資金調達 アプトポッドとマクニカ、資本業務提携 ハードウェアブランド "EDGEPLANT" 立ち上げ エッジコンピュータ "EDGEPLANT T1" の販売開始 アナログ信号利用を可能にするインターフェイス機器 "EDGEPLANT ANALOG-USB Interface" の販売開始 開発周りの拡充 iOS用のアプリケーション開発キット 「intdash SDK for Swift」の提供 可視化ダッシュボード「Visual M2M Data Visualizer」 最新バージョン及びパーツ開発キット「Visual Parts SDK」の提供 ... とけっこう今年の前半に固まった形にはなりましたが、さらなる資金調達も行い、組織規模も少しづつ大きくなってきています。 特にハードウェアブランド "EDGEPLANT" については大きなトピックで、小さい会社規模ながらのチャレンジでもあります。 今回のAdvent Calendarでもいくつかありました。 tech.aptpod.co.jp tech.aptpod.co.jp というところで! 今回の振り返りでも昨年同様、いろんなことがありすぎたので時系列というよりは、今年のトピックを個人的なところ含めていくつかピックアップして書いていきます。 組織 年初から将来を見据え事業を戦略的に実行するための組織の再編もあり、いろいろ紆余曲折ありながら進んできました。 またまた激動な年だったな と記憶ない部分もありますが💦 各マネージャやチームリーダーのおかげでグループやチームとしてよくなってきてチームとして作り上がってきている部分もあり、来年のさらなる飛躍に向けて今までのものをベースに積み上げていけたらと思っています。 ツール 昨年からトライアルしていた プロジェクト/課題管理のツールとして Jira(ジラ) ドキュメントコラボレーションツール、ナレッジ管理として Confluence(コンフルエンス) を全社導入しました。 Confluenceについては、最近では同じ領域のツールで Notion を使っている企業も多くなっていますが、Jiraとの親和性もあり、機能的にも遜色なく現在の利用においては、問題ありません。以前に比べるとだいぶ知の集約/構造化は進んできた感はあります。(まだまだこれからな部分もありますが。。) Notionについては、こんなものも出ているぐらいでだいぶ意識はしている感じですね。 www.atlassian.com 個人的にはNotion使っています👍 あとまだ一部の利用ですが、 アプリケーション向けのワイヤーフレーム作成やブレスト向けのコラボレーションツールとして Whimsical を使い始めています。 個人的には学習コストが低く、サクッと頭の中を整理したり形にしやすく、コラボレーションしやすいので重宝しています。 技術ピックアップ 個人的にはここのあたりをトピックとして。 www.aptpod.co.jp Visual M2M については、事業の当初から開発され、年々進化してきています。 2012年頃の1stから始まり、2ndとして2015年頃から今のVisual M2M Data Visualizerの開発が始まり今に至ります。 歴史があり長く継続して開発された製品となります。 特にフロントエンド界隈は進化が早く、トレンドも移り変わりやすいといったなかで、TypeScriptが浸透する前に、型宣言を導入するため Flow を使用したり。。等々試行錯誤しながらやってきた部分があります。 プロジェクト自体も巨大でなかなかテコ入れも難しい面もあり、 外部の開発者自身で拡張したいニーズも前々からずっとあった状態でした。 そんな中、担当の白金がFlow,TypeScriptを共存し本体の一部の機能を段階的に分離する方法を見出し、その最初として以前から要望の強かったVisualPartsの切り出し、SDK公開に至ったのでした。 事業的にも大きな機能でインパクトがあり、今後のVisual M2Mとしての光が見えたといったところでした。 Flow,TypeScript共存に関する詳細はまたテックブログにて! VisualPartsのテックブログはこちら↓ tech.aptpod.co.jp 他にもまだまだあったりするのですが、公開が来年なネタも多いのでここまでとします。 個人的に 個人的なところを少し。 今年は一回目のワクチン摂取で副反応起きたり、 引っ越ししてワークスペースを確保(生産性もアップ!)したり、 ... いろいろありました。 製品開発のWeb周りを埋めるために実装に入ったりのチャレンジもありました。(今も続いています) ざっと製品のWeb周りのスタックは TypeScript React Next.js *1 あたりを中心にマイクロフロントエンド *2 という小さいWebアプリが集まっているような構成です。 martinfowler.com 最初はうまく時間をとりつつ、学びつつ苦労しました。だいぶ慣れましたが、リリースする際に数が多いのでけっこう大変です💦 まだまだ恩恵が受けられていない部分や改善はありますが、やりがいはあると思います! というところで採用を挟んでおきます😅 open.talentio.com また、関連して、内部的に部署横串のフロントエンドのコミュニティ会を始めまして、 製品 / 技術 / 課題共有 問題の解決 / 相談(壁打ち) Webフロントエンドに関するトピックやブラウザ/ライブラリの更新状況 をトピックとして相互の助け合い、フロントエンドの技術追求、技術課題の解決や実装の効率化/スピードアップ、品質向上を目的として定期的に実施しています。 既に30回程度を重ね、振り返りしつつ改善を重ねて良い方向に向かっており、今年やってよかったものの一つでした。 2022年に向けて 2021年も期待の通りにはいかず、状況としても2020年と大きく変わることはなく、お客様の計画自体がズレることもあり、難しい状況は続きました。 ただ、EDGEPLANTの立ち上げやSDK等の開発環境の充実化を進めたことにより、2020年に比べ新規のお客様やパートナー様が増加し、基盤として少しづつですが積み上がってきています。 取り組んできたものが少しづつ実ってきた感触はあります。 来年2022年は、昨年より改善の兆しは見えてきていて、DXというのも浸透し一般的になりつつある *3 状況ですので、来年のネタもまだまだ仕込んでありますし、引き続き、アクセル踏んで頑張っていきます!💪 来年もまたアプトポッドにご期待ください!! メリークリスマス!🎄 *1 : ※すべてにNext.jsが使われているわけではありません。SPAもあったりします。 *2 : 参考: 『Micro Frontends』という記事を読んだのでまとめる *3 : 急速に拡大するDX市場の現状と今後の動向・対策を解説
aptpod Advent Calendar 2021 の 23 日目を担当する、製品開発グループ intadsh チームの佐藤 (TK)です。 多言語化されたアプリケーションのフォームの開発で Yup を使ったスキーマを作成する機会があり Formik や、 React Hook Form などのフォーム用のライブラリに適用する前に単体テストを実行したかったので作成してみました。 API ドキュメントをテスト仕様として定義するところから紹介したいと思います。 ゴール 技術 API ドキュメントからテスト仕様を定義する レッドパターンを洗い出す レッドパターンから文言を決める 多言語ライブラリを作成する 日本語 英語 スキーマのテストを書く スキーマを定義する テストを実行する ゴール 多言語されたバリデーションメッセージが定義されたスキーマのテストが通ること 技術 TypeScript i18next Yup Jest API ドキュメントからテスト仕様を定義する レッドパターンを洗い出す 使用するエンドポイントの仕様を読んで、レッドパターン (= バリデーションメッセージを出す条件) を洗い出します。 今回は [POST] /contacts というエンドポイントに対してリクエストを投げることとします。 サンプルのAPI ドキュメント name required なので、未入力の場合 age type が integer なので、数値かつ整数ではない入力の場合 0 以上になっているので、-1 以下の入力の場合 email type が string (email) なので、メールアドレスの形式ではない場合 message required なので、未入力の場合 レッドパターンから文言を決める 洗い出せたので、それぞれの条件で表示する文言を日本語と英語と決めていきます。 箇所 メッセージ (日本語) メッセージ (英語) 条件 name 名前を入力してください。 Please enter name. 未入力の場合 age 数値を入力してください。 Please enter a number. 数値ではない入力の場合 age 整数を入力してください。 Please enter an integer. 整数ではない入力の場合 age 年齢は 0 以上の整数を入力してください。 The integer of Age must be greater than or equal to 0. -1 以下の入力の場合 email メールアドレスが不正な形式です。 Email is in invalid format. メールアドレスの形式ではない場合 message メッセージを入力してください。 Please enter Message. 未入力の場合 これで、それぞれの条件に対応した表示したいメッセージが定義できました。 こちらをテスト仕様とし、次は多言語ライブラリを作成していきます。 ※ 今回はサンプルなので最低限のバリデーションを定義しています。 多言語ライブラリを作成する i18next の Basic sample を参考に文言を定義していきます。 import i18next from "i18next" ; import { translations as translationsEN } from "./en/translations" ; import { translations as translationsJA } from "./ja/translations" ; i18next.init ( { lng: "en" , debug: false , resources: { en: { translation: translationsEN , } , ja: { translation: translationsJA , } , } , } ); export { i18next } ; 今回は日本語と英語で言語ファイルをそれぞれ分けて定義しました。 表示したいメッセージは先程定義したので、それを当てはめていきます。 日本語 import { Translations } from "../types" ; export const translations: Translations = { "name.required" : "名前を入力してください。" , "age.invalid-type" : "年齢は数値を入力してください。" , "age.integer" : "年齢は整数を入力してください。" , "age.min" : "年齢は 0 以上の整数を入力してください。" , "email.invalid-format" : "メールアドレスが不正な形式です。" , "message.required" : "メッセージを入力してください。" , } ; 英語 import { Translations } from "../types" ; export const translations: Translations = { "name.required" : "Please enter name." , "age.invalid-type" : "Please enter a number." , "age.integer" : "Please enter an integer." , "age.min" : "The integer of Age must be greater than or equal to 0." , "email.invalid-format" : "Email is in invalid format." , "message.required" : "Please enter Message." , } ; 念の為、言語 (lng)と Key を指定した場合の挙動を確認しておきます。 Jest で簡単な単体テストを書きました。 import { i18next } from "." ; test ( "name.required en" , () => { i18next.changeLanguage ( "en" ); expect ( i18next.t ( "name.required" )) .toBe ( "Please enter name." ); } ); test ( "name.required ja" , () => { i18next.changeLanguage ( "ja" ); expect ( i18next.t ( "name.required" )) .toBe ( "名前を入力してください。" ); } ); ... テストを実行して問題ないようであれば、この言語設定を実際にスキーマに適用させていきます。 まずはスキーマのテストを書きます。 スキーマのテストを書く 「多言語されたバリデーションメッセージが定義されたスキーマ」が取得されることを期待しているので、 まずは、言語 (lng) を渡して、それに沿ったスキーマが返却されるような関数を想定します。 const { schema } = getSchema ( "en" ); これで指定した言語のスキーマを取得する関数が定義できたので、先に作ったテストパターンを使ってテストケースを定義していきます。 import { getSchema } from "./schema" ; describe ( "英語のスキーマ" , () => { const { schema } = getSchema ( "en" ); it ( "名前が未入力の場合" , () => { const testValues = { name: "" , email: "simple@example.com" , message: "message" , } ; expect (() => schema.validateSync ( testValues )) .toThrow ( "Please enter name." ); } ); it ( "年齢が数値ではない入力の場合" , () => { const testValues = { name: "name" , age: "age" , email: "simple@example.com" , message: "message" , } ; expect (() => schema.validateSync ( testValues )) .toThrow ( "Please enter a number." ); } ); it ( "年齢が整数ではない入力の場合" , () => { const testValues = { name: "name" , age: 0.1 , email: "simple@example.com" , message: "message" , } ; expect (() => schema.validateSync ( testValues )) .toThrow ( "Please enter an integer." ); } ); it ( "年齢が-1 以下の入力の場合" , () => { const testValues = { name: "name" , age: -1 , email: "simple@example.com" , message: "message" , } ; expect (() => schema.validateSync ( testValues )) .toThrow ( "The integer of Age must be greater than or equal to 0." ); } ); it ( "メールアドレスの形式ではない場合" , () => { const testValues = { name: "name" , age: 29 , email: "Abc.example.com" , message: "message" , } ; expect (() => schema.validateSync ( testValues )) .toThrow ( "Email is in invalid format." ); } ); it ( "メッセージが未入力の場合" , () => { const testValues = { name: "name" , age: 29 , email: "simple@example.com" , message: "" , } ; expect (() => schema.validateSync ( testValues )) .toThrow ( "Please enter Message." ); } ); } ); エラー条件に沿った値を schema.validateSync に引数で入れると、 ValidationError がスローされる ので、 Jest の .toThrow を使ってバリデーションメッセージを取得します。 さらに念の為、全ての値がエラー条件に沿った場合のテストケースも定義しておきます。 it ( "すべての値が不正な場合" , () => { const testValues = { name: "" , age: -1 , email: "Abc.example.com" , message: "" , } ; schema .validate ( testValues , { abortEarly: false } ) . catch (( validationError ) => { const validationErrors: Record < string , string > = {} ; const _validationError = validationError as Yup.ValidationError ; _validationError.inner.forEach (( error ) => { if ( error.path ) { validationErrors [ error.path ] = error.message ; } } ); expect ( validationErrors ) .toEqual ( { name: "Please enter name." , message: "Please enter Message." , age: "The number of Age must be greater than or equal to 0." , email: "Email is in invalid format." , } ); } ); } ); schema.validate のオプションに abortEarly: false をセットすることによって、 すべての検証が実行された後に ValidationError がスローされる ようになります。 スローしたエラー の inner から validationErrors としてオブジェクトを生成し、toEqual で検証します。 スキーマを定義する テストから実際にスキーマが取得できる関数を作成します。 import * as Yup from "yup" ; import { i18next } from "../i18n" ; import { Schema } from "./types" ; export const getSchema = ( lang: "en" | "ja" ) => { i18next.changeLanguage ( lang ); const schema: Yup.SchemaOf < Schema > = Yup. object () .shape ( { name: Yup. string () .required ( i18next.t ( "name.required" )), age: Yup. number () .transform (( value , originalValue ) => originalValue === "" ? undefined : value ) .typeError ( i18next.t ( "age.invalid-type" )) .integer ( i18next.t ( "age.integer" )) .min ( 0 , i18next.t ( "age.min" )), email: Yup. string () .email ( i18next.t ( "email.invalid-format" )), message: Yup. string () .required ( i18next.t ( "message.required" )), } ); return { schema } ; } ; テストを実行する スキーマとテストを定義したら、実際にテストを実行してみます。 PASS src/schemas/schema.test.ts (7.414 s) 英語のスキーマ ✓ 名前が未入力の場合 (16 ms) ✓ 年齢が数値ではない入力の場合 (30 ms) ✓ 年齢が整数ではない入力の場合 (1 ms) ✓ 年齢が-1 以下の入力の場合 (1 ms) ✓ メールアドレスの形式ではない場合 (1 ms) ✓ メッセージが未入力の場合 (1 ms) ✓ すべての値が不正な場合 (1 ms) ✓ すべての値が正常な場合 (1 ms) Test Suites: 1 passed, 1 total Tests: 8 passed, 8 total Snapshots: 0 total Time: 7.921 s すべてのテストケースが通ったので、最後に正常な値を入れたパターンのテストを定義します。 it ( "すべての値が正常な場合" , () => { const testValues = { name: "name" , age: 10 , email: "simple@example.com" , message: "message" , } ; expect ( schema.isValidSync ( testValues )) .toBe ( true ); } ); ✓ すべての値が正常な場合 (1 ms) このように多言語化されたアプリケーションのフォームの開発で Yup を使ったスキーマを作成することで、バリデーションがより安全に実装できます。 また、 Formik や、 React Hook Form に結合する前にテストをすることによってバリデーションの品質向上につながればと思います。
aptpod Advent Calendar 2021 の22日目を担当する、製品開発グループintdash チームの呉羽です。 以下の要件を満たした回数記録基盤を、以前に個人開発したので紹介します。 Bluetoothで接続されたIoTボタンが押された回数を記録したい ボタンが押された時刻と共に永続化したい 永続化されたデータを集計して閲覧したい 利用したもの 構築までの流れ IoTボタンの準備 Raspberry Piの準備 IoTボタンとの接続 IoTボタン押下時の処理 AWSの準備 Amazon Timestreamによる記録の永続化 API Gatewayによる記録実行URLの実装 集計結果をAmazon SNSを通じてメール通知 まとめ 利用したもの CamKix カメラシャッターボタン Raspberry Pi 4 AWS (Lambda, Timestreamなど) 構築までの流れ 今回は以下の図のように、ボタンを押してから永続化される流れとなります。 IoTボタンによる回数記録までの流れ IoTボタンの準備 今回はIoTボタンとして CamKix カメラシャッターボタン を利用します。 この製品はBluetoothで接続可能な スマホのカメラシャッター向けボタン ですが、実質的にはボリュームアップキーが1つあるキーボードです。価格は1000円ほど(2021/12/20確認)。 Raspberry Piの準備 IoTボタンとAWSを仲介するための機器として、今回はRaspberry Pi 4(OSはUbuntu Server)を利用します。Raspberry Piでなくとも、以下の要件を満たせば代替可能です。 IoTボタンをBluetoothで接続可能 インターネットを利用可能 IoTボタンとの接続 LinuxでBluetoothデバイスの管理をする際、 bluetoothctl というソフトウェアを用います。広く利用されているため接続手順は省略します。 IoTボタン押下時の処理 IoTボタンが押下された際、後述するAmazon API GatewayのURLを叩くことで、AWSとの橋渡しを行います。 今回は evdev-trigger を利用し、接続されたデバイスの特定のキーが押下された時にコマンドを実行させます。以下のような設定ファイルを用意すれば、IoTボタン押下時に、 curl コマンドが実行されます。 # physical id of device phys : a1:b2:c3:d4:e5:f6 triggers : # Key is the input event code to trigger the command. 115 : command : [ "curl" , "https://example.com" ] evdev-triggerはフォアグラウンドで動作するソフトウェアなので、supervisorによりバックグラウンドとして常に動作させておきます。以下がsupervisorの設定ファイル例です。 # cat /etc/supervisor/conf.d/evdev-trigger.conf [ program:evdev-trigger--camkix ] command= /usr/ local /bin/evdev-trigger --config /etc/evdev-trigger/camkix.yml autorestart = true AWSの準備 最後にAWS上で以下を構築します。 Amazon Timestreamによる記録の永続化 API Gatewayによる記録実行URLの実装 集計結果をAmazon SNSを通じてメール通知 実装コードやCloudFormationのテンプレートはGitHubで公開しているため、以下の手順でデプロイ可能です。 https://gist.github.com/hareku/30023706e854015bfa289bd4a2081022 Amazon Timestreamによる記録の永続化 今回はデータの保存先としてAmazon Timestreamを利用します。保存先として、他にDynamoDBも候補として挙げられますが、Timestreamは後述する集計機能を提供しているため、今回はTimestreamを選択します。 Timestreamは時系列データベースで、AWS SDKを通して容易に扱えます。 今回のGo言語による実装コードは github.com/hareku/go-timestreamer で公開しており、Lambdaのエントリーファイルは cmd/simple/lambda/record/main.go です。 API Gatewayによる記録実行URLの実装 上述のLambdaをHTTPプロトコル経由で実行させるために、Amazon API Gatewayを用います。 また不特定多数から呼び出されないよう、API Gatewayが提供しているAPIキーを設定することが推奨されます。 集計結果をAmazon SNSを通じてメール通知 TimestreamはSQL-likeな集計機能を提供しています。そこで集計結果を以下の手順で通知させます。 回数記録基盤の結果通知の流れ CloudWatch Eventsで定期的にLambdaを実行する 実行したLambda上でTimestreamの集計SQLを発行し、結果をAmazon SNSのトピックへ送信する Amazon SNSのトピックを購読しているメールアドレスに集計結果が送信される 以下のように、一日ごとの総記録回数がプレーンテキスト形式のメールで送信されます。 2021-11-27: 7 2021-11-26: 4 2021-11-25: 5 .... 今回は以下のようなクエリをTimestreamに投げました。 SELECT bin(time, 24h) as date, SUM(measure_value::bigint) as count FROM "db"."meas" WHERE time between ago(30d) and now() GROUP BY bin(time, 24h) ORDER BY date DESC Timestreamは時系列関連の関数が多く実装されており、簡潔に集計することが出来ました。慣れ親しんだSQLで表現力高く書けますので、気になる方はAWSドキュメントの Timestreamを使った集計のSQL例 の一読をおすすめします。 今回実装した集計用Lambdaのエントリーファイルは cmd/simple/lambda/publish-sns/main.go です。 まとめ 以上が、IoTボタンによる回数記録基盤の構築の流れです。例に挙げたコードでは、AWSの利用料金は月に1ドルも掛からないため(2021/12/20確認)、低コストで運用が可能です。
aptpod Advent Calendar 2021 の21日目を担当します、SREチームの金澤です。 私が所属する SRE チームでは、担当業務の一つに、弊社の製品である intdash のインフラストラクチャをメインとしたクラウドシステムのインフラ設計・構築、監視・バックアップなどの運用業務があります。 バックアップについては、約1年半前、Amazon Web Service(AWS) のマネージドサービスである AWS Backup への移行を開始し、設定のチューニングを行いながら、現在ではバックアップ要件がある AWS リソースほぼすべてを AWS Backup にてバックアップ管理しています。 本記事では、バックアップマネージドサービスである AWS Backup への移行について、現在検討中の内容も交えてご紹介いたします。 AWS Backup とは バックアップの動作 バックアップの設定検討 環境毎の Backup Vault の作成 IaC による管理が可能 バックアップリソースの選択 RDSの継続的バックアップ バックアップのスケールが容易になった 監視 監査 さいごに AWS Backup とは 公式ドキュメント の言葉を借りますと、 一元管理型クラウドバックアップサービス です。 AWS Backup は AWS クラウド内の AWS のサービス間でアプリケーションデータを簡単かつ低コストでバックアップできる一元管理型クラウドバックアップサービスです。 スナップショットとしてバックアップするケースが多い EBS を始め、RDS や EFS など現在11種類のリソースのバックアップが可能 *1 です。さらに、今年オンライン上で開催されました re:Invent 2021 では、Amazon S3 のバックアップがサポート予定であることが発表 *2 となり、計12種類となりました。 設定においては、複雑になりがちなバックアップウインドウ *3 や、保持しておきたいバックアップファイルの世代管理が容易に設定できます。また、マネージドサービス自体の初期費用はゼロで、使用したい場合は手軽に始めることができます。 バックアップの動作 AWS Backup の動作について簡単に説明します。 AWS Backup では、Backup Plan リソースの内容にしたがってバックアップの動作を決定し、バックアップリソースは Backup Vault というリソースに整理・保管されます。 AWS Backup で行われるバックアップ動作 Backup Plan では、バックアップの頻度やバックアップウインドウ、保持期間を定めます。また、バックアップ対象を指定する方法を設定します。 Backup Plan の設定内容 バックアップの設定検討 バックアップを行うにあたり、設定検討した内容です。 環境毎の Backup Vault の作成 通常、開発環境は本番やステージングなど複数の環境があるものと思います。それぞれの環境におけるバックアップの要件も当然異なるため、複数の Backup Vault を作成し運用しています。これにより、それぞれの環境におけるバックアップリソース個数の規模感が明確になり、現状どのくらいのバックアップリソースが存在するか把握できるようになりました。 Backup Vault の作成には個数制限 *4 があるものの費用自体は発生しないため、管理したい形で構成することができます。 環境毎の Backup Vault の作成 IaC による管理が可能 弊社のインフラは基本的に IaC *5 を行っており、Terraform にて構築・管理しています。 現在では、IaCによる管理も珍しくないものとなりもはや当たり前と言われそうですが、AWS Backup も Terraform によるコード化が可能で、弊社のインフラストラクチャ管理と親和性が高く難なく組み込むことができました。 バックアップリソースの選択 バックアップするリソースを選択する方法としては、上述の通り以下が存在します。 サービス全体(EC2, EBS, ...) リソースID リソースに付与されるタグ 今後変更が見込まれない環境やバックアップ対象が明確に決められている場合は、サービス全体やリソース ID によるバックアップの方が都合が良い場合が存在しますが、変更が見込まれる環境においてはスケールしやすい(バックアップ対象の増減が容易な)リソースタグによる管理が多いと思われます。弊社もリソースタグによる対象管理を行っています。 タグとは別に、AWS Backup では サービスのオプトイン というバックアップ対象を有効または無効化出来る機能があり、併用することでタグ付与における効率化が可能か、と検討していましたが、現状ではそこまで効率化が見込めず、検討時点では hashicorp/terraform-provider-aws が対応していなかったため、採用していません *6 。 RDSの継続的バックアップ AWS RDS は自身でバックアップウインドウを設定し自動バックアップを行うことが出来ます。この自動バックアップが AWS Backup によって管理できるようになりました。バックアップ管理を統合し易くなり、管理性の向上が見込めるようになりました。弊社で稼働している RDS は稼働当初より自動バックアップが設定していますが、そのような目的のもとに AWS Backup への移行を行っています。 統合後、自動バックアップは AWS Backup 管理である旨が記載される バックアップのスケールが容易になった AWS Backup への移行のモチベーションとして一番大きかったのは、それまで稼働していたバックアップシステムのスケーラビリティを改善したいというものでした。 以前は AWS SDK を使用してバックアップを行っていました。当初は問題ありませんでしたが、弊社製品を利用していただく機会が増えることにより、バックアップリソースの数も増加し、次第にスケールの限界が見えるようになりました。その影響を被る前にマネージドサービスへの移行を検討・開始し、現在に至ります。 約1年半前の移行検討を開始した時点と比較して、現在もバックアップリソースの数は増加していますが、バックアップ失敗といったアラートはほとんど見られず、バックアップのスケールに対して特別な意識を持たなくてもよくなりました。 監視 AWS Backup の設定を行いバックアップを開始することができました。次に、バックアップが正常に実行されているかどうかの監視はどのように行えばよいでしょうか。 いくつか方法はあるかと思いますが、弊社では AWS SNS で作成した SNS topic に Backup Vault からバックアップのレスポンスを Slack に通知することで行っています。 Slack に自動投稿されたバックアップ失敗アラート バックアップするリソースの数が多い場合は、フィルターをかけた方が視認性的にも費用的にも利点が多いです。現在はレスポンスに含まれる MessageAttributes の Key-Value の値でフィルターし、通知に使用しています。 監査 現在検討中の内容もご紹介します。 先日開催された AWS のイベントである re:Inforce 2021 では AWS Backup Audit Manager が発表されました。 *7 これは、現在動作しているバックアップの結果を既定のポリシーに従って評価する、またその評価結果をレポートする機能となります。 Audit という名称から想像できるとおり、バックアップにおける監査・コンプライアンスの対応に役立つ機能です。 既定のポリシーとは、 バックアップリソースがバックアッププランによって保護されているかどうか バックアッププランの最小頻度と最小保持期間が想定通りとなっているか など計5種類が設定されていて、バックアップしているリソースに対してポリシーを満たしているかどうか評価され、レポートを指定の S3 バケットに CSV または JSON の形式で配信することができます。このレポートをデータソースとして、Amazon QuickSight を利用したダッシュボードを作成し監査対応者などの第三者がリソースバックアップの状況を閲覧するようなこともできると考えています。 レポートを元に作成したバックアップステータスダッシュボード(のようなもの) また、リソースバックアップの成功を確認出来るという意味では、上述の監視にも利用出来るものと考えており検討を進めています。 一点、この AWS Backup Audit Manager は AWS Config を用いて実現しています。 AWS Config は AWS リソースの設定状況を記録するサービスとなり、 AWS Backup Audit Manager においては、 AWS リソースの設定状況の記録 既定のポリシーによる評価 の2点を AWS Config が担当しています。対象となるリソース数が多い場合は意図せず費用が膨らむ場合があります。そのため、導入には費用算出を考慮した上で検討したほうがよいです。今回は影響が微小となるように、別途用意されている個人アカウントでの検証を行いました。 さいごに 本記事では、AWS Backup への移行として、その際に検討した内容や現在検討中の内容を交えて紹介いたしました。バックアップという比較的地味な領域ではありますが、システム運用の面では重要で不可欠な領域だと考えます。引き続き、有用なサービスや機能の利用を検討しながら、効率的な基盤構築・運用に貢献できるよう努めていきたいと思います。 *1 : 2021/11/30に発表されたVMwareで実行される仮想マシン を含む *2 : https://aws.amazon.com/jp/about-aws/whats-new/2021/11/aws-backup-amazon-s3-backup/ *3 : AWS Backup 内では「バックアッププラン」と呼ばれている *4 : Backup Vault はリージョン毎に最大100個作成できる *5 : Infrastructure as Code *6 : 後にv3.18.0にて対応されました *7 : https://aws.amazon.com/jp/about-aws/whats-new/2021/08/aws-backup-audit-manager/