TECH PLAY

ニフティ株式会社

ニフティ株式会社 の技術ブログ

500

弊社では業務PCとしてノートPCが支給されますが、外付けキーボードやマウスを利用したい方は追加支給してもらうこともできます。ですが自分好みのキーボード・マウスを利用したい方は、各人の責任で持ち込んで利用することも認められています。 その流れで私は自作したキーボードを業務PCに接続して使っていますが、そのキーボードを作成した際の知見を少しまとめました。 アナログジョイスティックについて アナログジョイスティックは主に縦横4方向を直感的に操作するためのデバイスのうちon/offを表す2値スイッチではなく、方向の量をアナログ的に検出できるものです。 ぶっちゃけ日頃からよく見かけるゲームのコントローラーについていて自キャラを移動させるときに使うやつです。 最近はゲーム機の修理用で単体で利用できるモジュールが手ごろな価格で販売されています(例: サイトA 、 サイトB 、 サイトC )ので、その中からキーボード基板に固定しやすく、設置面積が小さく、高さが低いものとして写真の中で黄色枠で囲ったものを選びました。 (K-Silverと言うメーカのJP19の互換品のようです) これは元々両手に分かれて使うあのコントローラーで使われているものを基板で転用しやすくするために端子の形を変えたものではないでしょうか。端子の形を電子工作で扱いやすい通常のスルーホール端子に変えると同時に、基板に固定するための突起が付いているところがポイント高いです。 キーボードに取り付ける際、マウスだけの使い方では勿体ないと思い、出来れば折角4方向を指示できるのでカーソルキーの入力として利用したいと考えました。 qmkのアナログスティック 私は自作キーボードで qmk_firmware (Quantum Mechanical Keyboard)というオープンソースのファームウェアとその派生物を使っています。qmkでアナログジョイスティックを使う一般的な方法は、ゲームコントローラーのアナログスティックとする方法と、ポインティングデバイスとしてマウスカーソルを移動させるために使う2種類です。 カーソルキーとして使うにはスティックを倒しこんだ際にキーをONに、戻したり逆方向に倒した時にOFFにする必要があり、既存のプログラムでは対応できません。 動きを変えるには全面的に差し替えてしまうと今度はポインティングデバイスとして利用するのが難しくなる懸念があります。 このため、ポインティングデバイスのロジックが呼び出すアナログスティックの状態確認部分に手を入れることにします。 rules.mkで POINTING_DEVICE_DRIVER=custom を指定してからカスタムドライバの関数を定義します。 動作方針 自作キーボードは使用中に動作モードを変更する手段としてキーを押したときに入力される文字を変更するためのキーマップがあります。キーマップではキースイッチそれぞれを押した際にどのキーを入力したことにするかを割り当てるために文字コードに似たキーコードという物を使います。 そしてキーボードでマウスの代わりをするためにUSBを使ってPCに送ることはせず、qmk内部で異なる処理をしてマウスの動作を発生させるマウスキーと呼ばれる機能があり、専用のキーコードが存在します。 今回ジョイスティックをカーソルキーの代わりに使うと決めましたが、マウスの代わりにマウスカーソルを操作したくなることもあるかもしれませんので、マウスキーが指示された場合はマウスとして動くことにします。 ということで、下記2つの方針とします スティックを倒した時は各方向に指示されたキーコードを発生させる その方向に定義されたキーコードがポインティングデバイスの移動移動を扱う場合は倒し込み角度を返す qmkでデバイスドライバなど機能拡張を行う場合は、qmkのソースコード本体で(weak)宣言されている関数と同名の関数を定義することで差し替えることができます。 今回はquantum/pointing_device/ pointing_device.h を参考にしてポインティングデバイスを利用する際に使われる下記の関数を定義しました。 __attribute__((weak)) void pointing_device_driver_init(void); __attribute__((weak)) report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report); __attribute__((weak)) report_mouse_t pointing_device_task_user(report_mouse_t mouse_report); pointing_device_driver_init()では、スティックで利用するアナログ入力のピンを初期化ます pointing_device_driver_get_report()では、上記ピンの電圧を確認してスティックの倒しこみを検出し、スティックの向きとキーマップの定義内容から実行する動作をqmkに伝えます pointing_device_task_user()では、簡易的なドリフト対策をします 元々のqmkではquantum/pointing_device/ pointing_device.c と drivers/sensors/ analog_joystick.c の2層構造でしたが、両方のレイヤで加工が必要になってうまくまとめられなかったので quantum 側の関数でドライバー側の処理もしてしまうことにしてしまいました。 実装 ※コード全文は GitHub に置きました 処理のトリガは通常のマウス操作と同じく pointing_device_driver_get_report() が呼び出されることでトリガにします。この関数が呼び出されるたびに毎回マウスレポートを報告してしまうと過剰すぎるために間隔をあける必要があります。 timer_elapsed() は引数で示した時刻から経過時間を返すので、これがインターバル時間(ANALOG_JOYSTICK_READ_INTERVAL)を超えるまではスキップするようにしておきます。 次に analog_joystick_read() を呼び出してスイッチとして確認と動作を実行した上で処理が残ったポインターのアナログ的移動を取得します。 uint16_t lastCursor = 0; // 最終取得時刻 // ここでポインティングデバイスの状態を取得する report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) { if (timer_elapsed(lastCursor) > ANALOG_JOYSTICK_READ_INTERVAL) { lastCursor = timer_read(); report_mouse_t data = analog_joystick_read(); pd_dprintf("Raw ] X: %d, Y: %d, H: %d, V: %d\n", data.x, data.y, data.h, data.v); mouse_report.x = data.x; mouse_report.y = data.y; mouse_report.h = data.h; mouse_report.v = data.v; mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, data.buttons, POINTING_DEVICE_BUTTON1); } return mouse_report; } analog_joystick_read() は既存のqmkでは drivers/sensors/ analog_joystick.c で行う処理のレイヤですが、キーマップを確認して操作を依頼するという上位レイヤの処理が入ります。 (qmkの流儀に従うなら返り値として戻す方が好ましいでしょう) 倒しこみ量のアナログ値をスイッチとして判定する際、境界値の前後でon/offを割り当てるとチャタリングと似た症状が発生してしまいます。 このためにonにする値とoffに戻す値を分けて設定します。 ジョイスティック1本に対して必要な情報は、3種あります 確認するアナログピン2本 ※1 アナログピンのニュートラル位置 ※2 4方向の仮想的なマトリクス位置 ※3 その他に、内部データと状態保存のために下記3種の情報が必要でした マウスカーソルのための倒しこみと速度の対応表 ※4 マウスホイールのための倒しこみと速度の対応表 ※5 仮想キースイッチの状態マップ ※6 関数全体は以下のようになります。 static int8_t weightsCursor[101] = ANALOG_JOYSTICK_WEIGHTS; // ※4 static int8_t weightsWheel[101] = ANALOG_JOYSTICK_WEIGHTS2; // ※5 static pin_t adpins[HFJS_STKS] = HFJS_ANALOG_PINS; // ※1 int16_t origins[HFJS_STKS] = {0}; // ※2 uint8_t vmatrix = 0; // ※3 // 値を取得する・キー入力の場合はキーイベントを発生させる report_mouse_t analog_joystick_read(void) { static uint8_t rows[8] = HFJS_ROWS; // {6,7,5,8, 1,2,0,3} ※3 static uint8_t cols[8] = HFJS_COLS; // {9,9,9,9, 9,9,9,9} ※3 report_mouse_t report = {0}; uint16_t keycode; int16_t position; int8_t coordinate; int num; int flg; int8_t distance; for (int i = 0; i < HFJS_STKS; i++){ // ※a position = analogReadPin(adpins[i]); // ※b coordinate = axisCoordinate(position, origins[i], 0); // -100 ~ 0 ~ 100 の値に変換する ※c for (int n = 0; n < 2; n++){ // ※d distance = (n ? -1 : 1) * coordinate; num = i * 2 + n; flg = 0x1 << (num); // vmatrix のビットマップへの割り当て(BUG: 押している最中のレイヤ切替でリリースしていない) keycode = matrix_to_keycode(rows[num], cols[num]); switch(keycode){ // 現在設定されたキーコードを確認 case MS_DOWN: // ※e if(distance > 0){ report.y -= axisToMouseComponent(coordinate, origins[i], maxCursorSpeed, weightsCursor); } break; case MS_UP: // ※e if(distance > 0){ report.y -= axisToMouseComponent(coordinate, origins[i], maxCursorSpeed, weightsCursor); } break; case MS_RGHT: // ※e if(distance > 0){ report.x += axisToMouseComponent(coordinate, origins[i], maxCursorSpeed, weightsCursor); } break; case MS_LEFT: // ※e if(distance > 0){ report.x += axisToMouseComponent(coordinate, origins[i], maxCursorSpeed, weightsCursor); } break; (※マウスホイールについては省略) default: // キー入力として処理する if(vmatrix & flg){ // 前回押されていた時 ※f if(distance < HFJS_RELEASE) { // 下限以下ならリリース vmatrix &= ~flg; action_exec(MAKE_KEYEVENT(rows[num], cols[num], false)); switch_events(rows[num], cols[num], false); // 点灯させる位置を知らせるためにkeyboard.jsonでダミーのLEDを登録する } }else { // 未だ押されていない場合 ※g if(distance > HFJS_ACTION * (vmatrix ? HFJS_DBLACT : 1) ){ // 上限を超えたらプッシュ (斜め入力の際は HFJS_DBLACT で判定を緩和する) vmatrix |= flg; action_exec(MAKE_KEYEVENT(rows[num], cols[num], true)); switch_events(rows[num], cols[num], true); } } } } } return report; } 処理の流れとしては下記の様になっています。 ※a スティックごとに縦横のそれぞれ2回ずつ処理にする >※b アナログ値を取得 >※c -100 ~ 100 の割合値に加工 >※d アナログ値ごとに正の方向、負の方向の2回処理にする >>※e キーコードを確認してマウスカーソル関連だったら返答用reportに記録 >>※f 通常キーで前回onだった場合、キーコードが設定された方向を正として、off境界以下になっていたらキーを離したアクションを発生して記憶 >>※g 通常キーで前回offだった場合、キーコードが設定された方向を正として、on境界値以上になっていたらキー入力アクションを発生して記憶 こうすることで、通常時はカーソルキーだけれどレイヤーを変更するとマウスカーソルやホイール操作ができるようになったり、上下はマウスホイールで左右はブラウザバックなどと言った自由なキー割り当てが可能になりました。 さて、これを升目状に並べたら物理フリックキーボードが出来上がるわけですが、それはそれで大変そうなので将来の課題にさせてください。 ドリフト対策 今回アナログジョイスティックをあまり品質が良いものでないものを選んでしまったため、乱暴に扱った時などに値がおかしいことが頻繁に発生しました。これは携帯ゲーム機などでドリフト現象などと呼ばれているものです。これをプログラムで対処しようと思います。 簡易的なアプローチは2つあり、一つはホームポジションの0とする範囲を広げること。二つ目として異常を検出して修正することです。 ここでは2つ目のアプローチについて説明します。 ドリフトとはホームポジションがずれてしまい、操作をしていなくても一定の固定の操作が続いていると誤認してしまう状態ですので、ドライバーからの応答が一定時間続いたらドリフトと判断することにします。今回は3秒続いたらドリフトと判断してホームポジションを今の位置に変更しています。 static report_mouse_t last_mouse_report = {0}; // アナログジョイスティックの値を取得した際の処理 report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) { static uint32_t pointing_device_changed = 0; // 最後に移動した時刻 int32_t timer = timer_elapsed32(pointing_device_changed); // 移動からの経過時間 if(!(mouse_report.x == 0 && mouse_report.y == 0)) { // マウス移動は間欠的に発生して隙間は0,0になるので読み飛ばす if(memcmp(&mouse_report, &last_mouse_report, sizeof(report_mouse_t)) == 0) { // 変化が無かったら if (timer > 3000) { // 3秒越えたら pointing_device_driver_set_adjust(); // ポジションリセット(init同等の処理) pointing_device_changed = timer_read32(); } }else { // 移動した場合タイマーリセット pointing_device_changed = timer_read32(); } last_mouse_report = mouse_report; } return mouse_report; } この3秒ルールでドリフトに対してイラつくことが少なくなりました。 実際にはこの処理だけで全てのドリフトに対処できないため、特定の操作でポジションリセットを行っています。 おわりに 自作キーボードのファームウェアはソフトウェアと電子回路、機械的な事柄まで幅広い知識が身に着けられるのでソフトウェアエンジニアとして幅を広げるにはいい課題ではないでしょうか。 キーボードと言う毎日使うデバイスですので、活動の結果がQOLに直結する点も素晴らしいと思います。 この記事で興味を持って一人でも多くの仲間が現れることを期待しています。
アバター
弊社では業務PCとしてノートPCが支給されますが、外付けキーボードやマウスを利用したい方は追加支給してもらうこともできます。ですが自分好みのキーボード・マウスを利用したい方は、各人の責任で持ち込んで利用することも認められています。 その流れで私は自作したキーボードを業務PCに接続して使っていますが、そのキーボードを作成した際の知見を少しまとめました。 RGBマトリクス使ってますか? 私は自作キーボードで qmk_firmware (Quantum Mechanical Keyboard)というオープンソースのファームウェアとその派生物を使っています。 qmkにはフルカラーLEDをキースイッチごとに配置して流れたりヒートマップを表現するようなアニメーション処理があります。押したキーを中心にLEDの物理的な位置を考慮した光の流れは見ていて気持ちがいいものです。 ところが、ロータリーエンコーダーやその他のキーマトリクスに組み込まれていない入力を使うとLEDを発光させるきっかけがありません。 今回はロータリーエンコーダーを操作したときにLEDのアニメーションを動かそうと思います。 キー入力とLED処理 キーを押した時にLEDが光る仕組みは、quantum/keyboard.c の中でキーマトリクスのON/OFFを確認するループ matrix_task() に書かれており、スキャンが完了して各スイッチの状態変化をキューに積むループの中で switch_events() を呼び出して実現しています。これはLED側の(ソースコードの)処理かと思いきや、 keyboard.c に書かれていて LED_MATRIX と RGB_MATRIX の定義状況に応じて必要な処理をそれぞれ呼び出します。(つまり併存可能なのですね) アニメーションのきっかけになる位置を指定するのはkeymatrixではなくrgbmatrixの定義に記述されたrow,colを使っていました。キースイッチのmatrixが既に存在するのにledのmatrixでもrow,colを設定するのはとても面倒な作業ですが、プログラム的にシンプルな処理を目指す定義としては妥当なのでしょう。 このようにLEDのアニメーションを指示するための定義がキーマトリクスと別の定義になっていることで、次にご紹介する事が可能になっています。 マトリクスに無いキーを光らせる キーボードの余白などにLEDを置いた際、rgbmatrixで指定するmatrix位置はどうしたらよいでしょうか? 設定しないでおくことも可能ですが、私としてはキーマトリクスの未使用の交点を設定しておくことをおすすめします。ダミーでもrow,colを定義しておくことで、そのLEDを狙ってアニメーションを開始させることができます。 (ダミー定義:実際には0,4と0,5はスイッチをつけていませんが、LEDを設置しています) "layout": [ (略) { "flags": 1, "matrix": [0, 4], "x": 174, "y": 64 }, { "flags": 1, "matrix": [0, 5], "x": 194, "y": 64 }, 具体的にはLEDを登録する際、キーマトリクスで不使用のrow,col位置をmatrixタグに他のLEDと同じように設定するだけです。例えばロータリーエンコーダーのプッシュスイッチに反応させるときなどに使えないでしょうか。 (もっともその場合はプッシュスイッチ自体をマトリクスに組み込んでそのrow,colを指定する方がシンプルですが) LEDが無い位置をアニメーションの起点にする もう少し自由度をもとめて、ロータリーエンコーダーを回した際にスイッチの少し右側や左側がアニメーションの始点にするにはどうしたらよいでしょうか? それぞれの始点にLEDを設置することも一つの解ですが、ここでは存在しないLEDを設定する方法をご紹介します。シリアルLEDは長いデータを送ると個々のLEDが必要なデータを受け取って、それ以降の残りは次のLEDに渡すことでシリアルに処理する仕様です。 LEDの故障や配線ミスで途中までしか光らないことに遭遇した経験はないでしょうか?このように末尾のLEDは後続が居なくても正しく発光できます。この性質を利用して、実際に存在しないLEDを定義することができます。そしてその定義に設定されたマトリクスの座標を使ってqmkはアニメーションの計算を行ってくれるのです。 具体的にはLEDを正しく登録した rgb_matrix -> layout の末尾にロータリーエンコーダーの座標の左右に相当するダミーのLEDを、これまたダミーのキーマトリクスのものとして登録します。 ダミーLEDの登録は以下のようにします "layout": [ (略) { "flags": 1, "matrix": [0, 4], "x": 174, "y": 64 }, { "flags": 1, "matrix": [0, 5], "x": 194, "y": 64 }, ※ここまで存在するLEDの定義 ※以下はエンコーダー用のダミー定義(x座標を左右にずらしている) { "flags": 4, "matrix": [1, 6], "x": 168, "y": 64 }, { "flags": 4, "matrix": [0, 6], "x": 180, "y": 64 }, { "flags": 4, "matrix": [1, 7], "x": 188, "y": 64 }, { "flags": 4, "matrix": [0, 7], "x": 200, "y": 64 }, そしてロータリーエンコーダーのキー自体は以下のようにダミースイッチを登録します {"label": "1,6", "matrix": [1, 6], "x": 11, "y": 5, "w": 0.5}, {"label": "0,6", "matrix": [0, 6], "x": 11.75, "y": 5, "w": 0.5}, {"label": "1,7", "matrix": [1, 7], "x": 12.25, "y": 5, "w": 0.5}, {"label": "0,7", "matrix": [0, 7], "x": 13, "y": 5, "w": 0.5}, そして、ロータリーエンコーダーでスイッチが発生した際にLEDのアニメーションを指示するため encoder_update_user() の中で LED にアニメーションの開始を指示します。 (switch_events()定義 ※quantum/keyboard.cからの複製です) // RGBアニメーション開始処理(keypressのon/offはセットじゃなくてもよい) // この関数は quantum/keyboard.c からの複製です inline void switch_events(uint8_t row, uint8_t col, bool pressed) { #if defined(LED_MATRIX_ENABLE) led_matrix_handle_key_event(row, col, pressed); #endif #if defined(RGB_MATRIX_ENABLE) rgb_matrix_handle_key_event(row, col, pressed); #endif } // エンコーダーのキー位置を配列で定義 static const uint8_t encoder_a_rows[NUM_ENCODERS] = {1,1}; static const uint8_t encoder_a_cols[NUM_ENCODERS] = {7,6}; static const uint8_t encoder_b_rows[NUM_ENCODERS] = {0,0}; static const uint8_t encoder_b_cols[NUM_ENCODERS] = {7,6}; // エンコーダーのイベント処理(デフォルトを有効にする場合 encoder_update_userを実装してindexを読む) bool encoder_update_user(uint8_t index, bool clockwise) { int8_t row = clockwise ? encoder_a_rows[index] : encoder_b_rows[index]; int8_t col = clockwise ? encoder_a_cols[index] : encoder_b_cols[index]; tap_code_delay(matrix_to_keycode(row, col), 10); // RGBアニメーション開始(offで点灯する場合もあるので両方書いている) switch_events(row, col, true); // 点灯させる位置を知らせるためにkeyboard.jsonでダミーのLEDを登録する switch_events(row, col, false); return false; } ロータリーエンコーダーは通常のキースイッチよりスイッチ発生頻度が高いので賑やかですが、目的の回転方向によってアニメーションの起点を変えることができました。 おわりに 自作キーボードのファームウェアはソフトウェアと電子回路、機械的な事柄まで幅広い知識が身に着けられるのでソフトウェアエンジニアとして幅を広げるにはいい課題ではないでしょうか。 キーボードと言う毎日使うデバイスですので、活動の結果がQOLに直結する点も素晴らしいと思います。 この記事で興味を持って一人でも多くの仲間が現れることを期待しています。
アバター
弊社では業務PCとしてノートPCが支給されますが、外付けキーボードやマウスを利用したい方は追加支給してもらうこともできます。ですが自分好みのキーボード・マウスを利用したい方は、各人の責任で持ち込んで利用することも認められています。 その流れで私は自作したキーボードを業務PCに接続して使っていますが、そのキーボードを作成した際の知見を少しまとめました。 ロータリーエンコーダーとは 回転する軸の角度の変化を入力として扱うデバイスです。 回転軸を扱うものとしては他に 固定した範囲の中で角度をアナログ値として扱うボリューム いくつかに区切った角度をDIPスイッチのように扱うロータリースイッチ などがありますが、ロータリーエンコーダーは無限角度、相対変化と言う特徴があります。 PC関連では古くは 機械式マウス や、最近ではマウスホイールなどで用いられています。 ロータリーエンコーダーの特性 電気的な接点を使うものでは、キーボードのキースイッチと同じようにバウンス(チャタリング)が発生します。デバウンスの影響でA相B相の推移順が崩れてしまうと一瞬止まったようになったり逆行してしまったりします。 私は自作キーボードで qmk_firmware (Quantum Mechanical Keyboard)というオープンソースのファームウェアとその派生物を使っていますが、キースイッチのマトリクスを処理する場面ではソフト的に変化を測定して対処する処理が quantum/debounce/ の中に記述されています。 これに対してロータリーエンコーダーの処理は drivers/encoder/encoder_quadrature.c に encoder_wait_pullup_charge() と言う関数が記述されていることなどから、ハードウェア側でコンデンサーを使った対応を想定していることが分かります。このため、エンコーダーのA相B相で2本のGPIOが必要でコンデンサーのチャージを考慮して可能な限り専有させておく必要があります。 また、キースイッチのようにマトリクスを考慮するならチャージ時間より切替サイクルを長くとる必要があるのではないでしょうか。 このように、qmkではロータリーエンコーダーのドライバはデバウンス処理済のデータを出力する必要があります。 キーマトリクスへの組み込み このような背景があるロータリーエンコーダーですが、キーボードを設計し配線していると、どうしても配線が使うスペースが無駄に広い印象を持ってしまい、どうせスイッチなのだからキーマトリクスと共用できないのか?と試してみました。 (前提としまして使っているマイコンはRP2040のため、処理速度が比較的速いためうまくいっている可能性がありますので、他のマイコンをご利用される際には予めご自身での検証をお願いします。) 普通のキースイッチとロータリーエンコーダーが違うところは電気接点として見るとA相B相のGNDが共有されている点だけとなります。私が日頃使うCOL2ROWではGND端子をCOL側に接続してA,Bの端子の先にダイオードをつけて異なるROWに接続すると問題が無さそうです。 マトリクスのキーの状態は quantum/keyboard.c から呼び出された matrix_common.c, matrix.c の中でスキャンされてデバウンスした後、 matrix[] に保存されています。このデバウンスされた入力結果をロータリーエンコーダーの処理に引き渡せばいいのではないでしょうか? と言うことで、ロータリーエンコーダーのドライバーを上書きします。 まずはマトリクススキャン時に必要な接点の情報を保存します。 // エンコーダーの総数を定義 #define NUM_ENCODERS 2 // エンコーダーのキー位置を配列で定義 static const uint8_t encoder_a_rows[NUM_ENCODERS] = {1,1}; static const uint8_t encoder_a_cols[NUM_ENCODERS] = {7,6}; static const uint8_t encoder_b_rows[NUM_ENCODERS] = {0,0}; static const uint8_t encoder_b_cols[NUM_ENCODERS] = {7,6}; // スキャン結果を保存するバッファ bool encoder_a_state[NUM_ENCODERS] = {0}; bool encoder_b_state[NUM_ENCODERS] = {0}; // スキャン生データを取得 extern matrix_row_t raw_matrix[MATRIX_ROWS]; inline bool raw_matrix_is_on(uint8_t row, uint8_t col) { return (raw_matrix[row] & ((matrix_row_t)1 << col)); } // キーマトリクスのスキャン結果から必要分を退避 void matrix_scan_user(void) { // エンコーダーのピンの状態を記録 for (uint8_t i = 0; i < NUM_ENCODERS; i++) { encoder_a_state[i] = raw_matrix_is_on(encoder_a_rows[i], encoder_a_cols[i]); encoder_b_state[i] = raw_matrix_is_on(encoder_b_rows[i], encoder_b_cols[i]); } } また、ロータリーエンコーダーのドライバが値を読み取る関数が(weak)定義なので、これを上書きします。 回りくどくドライバ関数を上書きしているように見えるかもしれませんが、encoder_quadrature_read_pin() の結果はencoderの処理のなかでソフトウェア的なキューとデバウンスの処理をしているように見えたので、できるだけ通常の処理に合わせるためにこのようにしています。 // お勧めのコンデンサチャージ時間を待機してくれる便利関数 // とりあえずつぶしておく void encoder_wait_pullup_charge(void){ } // 固定ピンのためにinput highにしてしまうからつぶしておく void encoder_quadrature_init_pin(uint8_t index, bool pad_b){ } // エンコーダーのピン状態を記録したテーブルを読み込んで 0 か 1 を返す uint8_t encoder_quadrature_read_pin(uint8_t index, bool pad_b){ uint8_t ret; if(pad_b){ ret = encoder_b_state[index]; } else { ret = encoder_a_state[index]; } return ret; } マトリクスの中のスイッチがONになると設定されたキーコードが発行されてしまうのですが、これを避けるための手段は2つあり、一つは process_record_user() を定義して目的のキーコードの場合にfalseを返して以後の処理をスキップする方法、 もう一つは matrix_mask を定義してスキップしたい接点のビットをオフにする方法です。 対象のキーボードですでにどちらかの手段を利用している場合はその中に組み込むのが効率が良いと思います。どちらか迷った場合は matrix_mask の方が設定が少し複雑ですが処理するための計算量が少ないのではないでしょうか。 また、quantum/ 側の encoder_update_user() でfalseを返すと通常の処理をスキップできるため、この中で目的の処理を行います。 // エンコーダーのイベント処理(デフォルトを有効にする場合 encoder_update_userを実装してindexを読む) bool encoder_update_user(uint8_t index, bool clockwise) { int8_t row = clockwise ? encoder_a_rows[index] : encoder_b_rows[index]; int8_t col = clockwise ? encoder_a_cols[index] : encoder_b_cols[index]; tap_code_delay(matrix_to_keycode(row, col), 10); return false; // tap_code_delay() でスイッチとして処理しているためencoderで処理させない } エンコーダーを操作した結果の入力については、私は設定と変更の容易さからA相B相に割り当てた位置のキーコードを発行しました。 おわりに 自作キーボードのファームウェアはソフトウェアと電子回路、機械的な事柄まで幅広い知識が身に着けられるのでソフトウェアエンジニアとして幅を広げるにはいい課題ではないでしょうか。 キーボードと言う毎日使うデバイスですので、活動の結果がQOLに直結する点も素晴らしいと思います。 この記事で興味を持って一人でも多くの仲間が現れることを期待しています。
アバター
弊社では業務PCとしてノートPCが支給されますが、外付けキーボードやマウスを利用したい方は追加支給してもらうこともできます。ですが自分好みのキーボード・マウスを利用したい方は、各人の責任で持ち込んで利用することも認められています。 その流れで私は自作したキーボードを業務PCに接続して使っていますが、そのキーボードを作成した際の知見を少しまとめました。 自作キーボードで使うソフトウェアについて 私は自作キーボードで qmk_firmware (Quantum Mechanical Keyboard)というオープンソースのファームウェアとその派生物を使っています。 qmkのキー入力処理を大まかに俯瞰すると、以下のような流れになります まず大元になるメインループがあります。 そこから呼び出した関数でキースイッチのマトリクスを確認します。 前回確認した状態からマトリクスに変化があれば、それぞれの変更点についてactionを発行してキューに積みます。 最後にUSBデータを送信します。 マトリクスを確認してactionを発行する際に自作キーボード特有のTapDance(1つのキーに複数の機能を割り当てる機能)やCombo(複数のキーの組み合わせで特定の機能を呼び出す機能)などの便利機能に展開するようです。 キーの入力マトリクス qmkはC言語で記述され、上記マトリクスの状態はグローバル配列変数になっていました。 ということは、同じプログラムの中なら別のソースファイルからも参照することができます。 キーマップを参照するには、keymap.cファイルなどで以下の宣言をします。 extern matrix_row_t raw_matrix[MATRIX_ROWS]; これでマトリクスを参照できるようになるので、データをそのままOLED(有機ELディスプレイ)の表示関数 oled_write_raw_P() に渡すことで、まばらな点の集まりですがキーの入力状態を確認できるようになりました。 oled_write_raw_P((char*)raw_matrix, sizeof(raw_matrix)); 処理中のデータを直接リアルタイムで確認できるOLEDは、デバッグやトラブル調査で非常に便利です。(画像では斜めに点が並んでいますが、この点は総当たりマトリクスのcolとrowを繋ぐダイオードの影響です。) キースイッチ以外のキー入力手段 最初にお話ししたキースキャンの流れから、適切なドライバが無いデバイスや、既存のドライバと挙動を変えたいときには、以下の方法があります ユーザの操作でon/offを意図的に操作したりキーリピートを発生させる場合には、action_exec() でonとoffを都度発行します。 単発のキー入力ができればよい場合には、on/offをセットで発行してくれる tap_code_delay() を使います。これにより、瞬間的なピークを抑えつつ入力に必要な時間間隔でonとoffを発行してくれます。 ダイナミックキーマップ変更 自作キーボードを調べて回ると、ファームウェアをビルドする際にキーと入力されるキーコードの対応を決定するだけでなく via, remap, vialなどのツールを使ってキーマップを動的に変更できるようにしておくことが求められているようです。 これらのツールは基本的に以下の方法で動作します キーマトリクスの範囲で確保された配列を用意します。 マトリクスのrow(行)とcol(列)の交点を結びつけた表を作成します。 PCからRAW HIDの手順で書き換え指示を受け取る 上記の表を書き換えることでキーマップを変更します。 ツールを使った場合でもマトリクスの交点位置を動的に変更することは無いため、キーボードごとに一貫した固有のものを決めておき、どのように利用するかを工夫するところがファーム作成の醍醐味と感じています。 未実装スイッチとマトリクススキャン マトリクスのなかでスイッチを実装していないだけであれば悪影響の可能性は低いですが、rowとcolを共用してダイオードで意味を読み替える形の総当たりマトリクス(改良二乗マトリクスなど他の呼び方もあります)を使う際、row=colの位置は読み飛ばす必要があるため、「#define MATRIX_MASKED」を設定します。 MATRIX_MASKEDについてはじめは誤解していたのですが、スキャンを抑止するのではなくスキャンの結果入力されていても無視する挙動となっております。これは時間的制約が厳しいGPIO操作周辺のコードをシンプルに保ちつつ、効率を高めるためではないかと考えます。 この挙動はquantum/matrix.cの matrix_scan() のスキャン部分と、quantum/matrix_common.cの matrix_get_row() でスキャン結果をマスクして返答することから確認できます。 入力されたキーを無効として扱う方法は以下のような方法がありますが、計算量を考慮するとMATRIX_MASKEDが軽量で最適です MATRIX_MASKEDを使用する process_record_user()で発生したキーコードを否定する process_record_user()でmatrix[]配列を直接操作する また、マスクされた位置を読み出しで無効化するのみで他の箇所で判定しない現在の実装は、未実装スイッチを他のスイッチデバイスの入力に転用することがやりやすくなっていますが、その話はまた別の機会にお話しさせていただきたいと思います。 おわりに 自作キーボードのファームウェアはソフトウェアと電子回路、機械的な事柄まで幅広い知識が身に着けられるのでソフトウェアエンジニアとして幅を広げるにはいい課題ではないでしょうか。 キーボードと言う毎日使うデバイスですので、活動の結果がQOLに直結する点も素晴らしいと思います。 この記事で興味を持って一人でも多くの仲間が現れることを期待しています。
アバター
はじめに こんにちは。ニフティ株式会社の島田です。 今回はGitHub ActionsステータスのかっこいいバッジをREADMEに出す方法について紹介します。 ※こちらの記事は社内のレポートDBから転載した記事です これは何? READMEにこういうかっこいいステータスバッジをつけられるようになります。 GitHub Actionsのステータスを表示する 実はこれ、とても簡単です。 GitHub Actionsのワークフロー設定画面から、「Create status badge」を押せばリンクが取れますので、それをREADME.mdに貼るだけです。 こんな風にするとバッジをクリックした際のリンク先が、画像ではなくワークフローの詳細ページになって嬉しいです。 [![build and test](<https://github.com/${repository}/actions/workflows/CI-Master.yaml/badge.svg>)](<https://github.com/${repository}/actions/workflows/CI-Master.yaml>) 自作のステータスを出す(Coverageとか) バッジ shields.ioというサービスを利用すると、好きな情報をバッジにできます。 以下のような形式のurlを作るといい感じのsvgが取れます。 <https://img.shields.io/badge/go%20report-A+-brightgreen.svg?style=flat&logo=go> なんとなく以下みたいな感じです。 もちろんurlエンコードは必要です: https://shields.io/badges <https://img.shields.io/badge/${title}-${status}-${color}.svg?style=flat&logo=${logo}> Shell script バッジを組み立てるために色々します。 例えばこんな感じです。 grade=$1 case $grade in A+) curl -o go-report.svg "https://img.shields.io/badge/go%20report-A+-brightgreen.svg?style=flat&logo=go" ;; A) curl -o go-report.svg "https://img.shields.io/badge/go%20report-A-green.svg?style=flat&logo=go" ;; B) curl -o go-report.svg "https://img.shields.io/badge/go%20report-B-yellowgreen.svg?style=flat&logo=go" ;; C) curl -o go-report.svg "https://img.shields.io/badge/go%20report-C-yellow.svg?style=flat&logo=go" ;; D) curl -o go-report.svg "https://img.shields.io/badge/go%20report-D-orange.svg?style=flat&logo=go" ;; E) curl -o go-report.svg "https://img.shields.io/badge/go%20report-E-red.svg?style=flat&logo=go" ;; F) curl -o go-report.svg "https://img.shields.io/badge/go%20report-F-red.svg?style=flat&logo=go" ;; *) exit 1 ;; esac Actions ここが肝です。 適当にcurlとかでsvgファイルを出力するようにします。 svgファイルのみをaddしてpush先をmasterでないブランチにします。 これは、svgのみをcommitしたいという理由と、masterにpushすると常に画像の差分が出てしまうのを避けるためです。 name: badge on: push: branches: [ master ] jobs: goreportcard: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true - name: Setup goreportcard run: | mkdir /tmp/goreportcard cd $_ git clone https://github.com/gojp/goreportcard.git cd goreportcard make install go install ./cmd/goreportcard-cli - name: Generate go-report.svg run: | goreportcard-cli /bin/bash grade.sh `goreportcard-cli -j | jq -r ".GradeFromPercentage"` - name: Generate coverage.svg run: | go test ./internal/... -covermode=count -coverprofile=coverage.out fmt go tool cover -func=coverage.out -o=coverage.out cat coverage.out rate=$([[ `grep "total:" coverage.out` =~ [0-9]+.[0-9]%$ ]] && echo $BASH_REMATCH | cut -d% -f1) /bin/bash coverage.sh $rate - name: Push run: | git config user.name "bot" git config user.email "bot@example.com" git add go-report.svg coverage.svg git commit -m "generated" git push -f -u origin master:badge README.md README.mdで、バッジを生成・管理しているブランチのSVG画像を参照するように記述します。 [![Go Report](<https://github.com/${repository}/blob/badge/go-report.svg?raw=true>)](<https://github.com/${repository}/actions/workflows/badge.yaml>) だいたいこんな感じです。 [![Go Report](<https://github.com/${repository}/blob/${branch}/${file}?raw=true>)](<https://github.com/${repository}/actions/workflows/${workflow}>) おわりに 今回はGitHub ActionsステータスのかっこいいバッジをREADMEに出す方法について紹介しました。 GitHub Actionsのステータスのバッジをつけてみたい方はぜひ使ってみてください。 参考 https://shields.io/ https://qiita.com/ma91n/items/6c572c5887a50223c2b1
アバター
この記事は、リレーブログ企画「CI/CDリレーブログ」の記事です。 はじめに 初めまして! マイ ニフティチームの寺島です。 普段はスマートフォン向けのアプリケーション開発に携わっています。 ブログ運営チームのメンバーでもあります! 今回はリレーブログのアンカーとして、ブログチーム代表として走らせていただきます! CI/CDのリレーブログを盛り上げるという大いなる目的のために、前々から気にはなっていたのだけれど中々重い腰を上げられなかった、Xcode CloudのCI/CDを体験してそれをブログにしてみました! 一石二鳥ですね!! そもそもXcode Cloudとは Xcodeと呼ばれるApple製のIDEを利用して、CI/CDを簡単に行えるようにするサービスや仕組みのことです。 Xcode Cloudは、Xcodeに組み込まれた継続的インテグレーションおよびデリバリーサービスで、Appleデベロッパのために設計されたものです。アプリのビルド、複数の自動テストの並列実行、テスターへのアプリの配信、ユーザーフィードバックの表示と管理に役立つ、クラウドベースのツールを一か所で利用できるため、高品質なアプリの開発と配信が高速化されます。 https://developer.apple.com/jp/xcode-cloud/ 目指す形 TestFlightの公開まで目指します! コード変更→mainへ反映→反映の検知→テストの実行→TestFlight公開 の流れにしたいと思います。 今回は、iOSアプリで確認したいと思います。 前提条件 GitHubを利用します ブランチはmainブランチのみを利用します Xcode Cloudを利用します Apple Developer Programに参加したアカウントを利用します iOSアプリの作成を確認します 事前準備 新しいプロジェクト(iOSアプリ用)を立ち上げます。 GitHubのリポジトリも新規に用意します。 簡単な表示を行うViewと、表示に使用するメソッドのテストを作成します。 作成したコードは用意したリモートリポジトリにPushします。 初回は、リポジトリの紐付けを行うとWorkflowが実行されBuildされます。 初回以降は、mainブランチの変更を検知or手動でWorkflowが実行されるようになります。 ディレクトリ構成(完成系イメージ) こちらはあくまでイメージとなります。 Xcode上で表示されるファイルと合わせた形になります。 またInfo.plistは設定を入れていないと表示されないので、プロジェクト立ち上げ時になくても問題ありません。 後の手順で設定を行うと出てきます。 . ├── SampleProject006 │   ├── Info.plist │   ├── Preview Content │   ├── Assets.xcassets │   ├── ContentView.swift │ └── SampleProject006App.swift └── SampleProject006Tests     └── SampleProject006Tests.swift 利用コード ContentView helloメソッドの戻り値をTextでViewに表示するようにしています。 import SwiftUI struct ContentView: View { var body: some View { VStack { Text(hello()) } } } func hello() -> String { return "Hello, Swift!" } #Preview { ContentView() } SampleProject006Tests helloメソッドの戻り値が Hello, Swift! であることを確認するテストです。 import Testing struct SampleProject006Tests { @Test("一致することをテスト") func match() { #expect(hello() == "Hello, Swift!") } } GitHubへ反映 今回はmainをそのまま変更→Pushする形で確認します。 リモートブランチへ新しいプロジェクトをPushしておきます。 Xcode Cloudを試してみる Xcode Cloudをリモートリポジトリへ紐づける Buildを確認する Xcodeとリポジトリの紐付け XcodeからXcode Cloudを利用したいプロジェクトを選択します Workflowはデフォルト設定のままにします 利用している開発者アカウントをXcode Cloudが利用できるようにします 開発者アカウントとGitHubの組織を紐づけます 紐づけた組織内のリポジトリと連携します Xcodeのサイドバーから「Get Started…」を選択します。 新しいウィンドウが立ち上がるのでプロダクトを選択し、「Next」を選択します。 Workflowの詳細設定についての項目が選択できるウィンドウが表示されますが、今回こちらはデフォルトのまま先に進めます。(後ほどweb上で設定を行います。) 「Next」を選択します。 ここでリポジトリを選択するウィンドウが表示されます。 「Grant Access…」を選択します。(次からXcode以外での設定になります。) 自動でブラウザが立ち上がってきます。 まずはApp Store Connectへログインします。 Xcode CloudをGitHubに接続する画面が表示されます。 ここから、具体的なアカウントへGitHubへの接続の許可と、リポジトリの紐付けを行なっていきます。 「GitHubでステップ1を終了する」を選択します。 今度はGitHubのページが開きます。 Xcodeをインストールしたい組織を選択します。 ここで選択するのは、先ほど作成したリポジトリの所属する組織です。 さらに、リポジトリを選択します。 「All repositories」ですべてのリポジトリへのアクセスを許可するか、「Only select repositories」で特定のリポジトリのみに権限を与えるかを選択します。 今回は、Allで与える理由が特にないので先ほど用意したリポジトリのみに許可を与えます。 選択したら「Install」を実行します。 「Install」選択後、正常に接続されましたと表示したら完了です。 Xcodeに戻って、先ほど紐付けを行ったリポジトリに緑のチェックマークが入っていることが確認できます。 アプリを一度もApp Store Connectへ連携していないと、初回に作成を確認するダイアログが表示されます。 作成しないとXcode Cloudは利用できないので、「Complete」で作成を許可します。 最後に、「Start Build」で完了です! これで、Xcode Cloud上でBuildが走ります。 今回は特にWorkflowを設定していないので、シンプルなBuildが実行されて終了します。 App Store Connectで確認すると下記のような画面になります。 ステータスに緑のチェックマークが入っていれば無事にBuild完了です。 Xcode CloudとGitHubのリポジトリが正しく連携できています。 Xcode Cloudの設定を変えて試してみる 無事に連携できたので、色々試してみたいと思います。 TestFlightへ自動で公開するように設定を変更します Build時にテストを実行するように設定を変更します テストの項目を増やします 変更内容をmainへpushします 実行内容を確認します TestFlightへの公開が失敗しているので、修正して再度Buildします 実行内容を確認します App Store ConnectのXcode Cloudタブのサイドバーから「ワークフローの管理」を選択します。 今回作成したワークフローである「Default」を選択します。 Defaultワークフローの設定ページへ遷移するので、アクションセクションの中からアーカイブ項目を選択します。 デフォルトでは配信準備が「なし」に設定されていますので、今回の目標である「TestFlight(内部テストのみ)」を選択します。 続いてテストの実行を追加します。 アクションセクションのタイトルの横にある「+」マークをクリックして「テスト」を選択します。 新しい、テストに対する入力欄が追加されるので必要項目を入力していきます。 下記の画像のようになるようにします。 基本的には、最初に作成したテストについて適用していく形になります。 テストコードを追加します。 helloメソッドの返す値が一致しないことを確認するテストを追加します。 @Test("一致しないことをテスト") func doesNotMatch() { #expect(hello() != "Hello, World!") } 変更をcommitしてmainブランチをpushします。 mainブランチ以外で作業している場合は、mainブランチへmargeします。 mainが更新されるとWorkflowが実行されて、先ほど追加した「TestFlight」への公開と「テストの実行」が行われます。 今回は失敗します。 失敗の原因を確認します。 ステータスの赤丸バツ印マークをクリックすると詳細ページへと遷移します。 先ほど追加したテストは問題なく実行が完了してそうです。 Archiveでエラーが出ているみたいなので、「Archive – iOS」をクリックして中身を確認します。 中身を確認すると詳細なエラー内容が表示されます。 今回は、TestFlightに公開を行うのにアプリケーションにアイコンを設定していないのが原因のようです。 AssetsからApplconを追加します。 再度、変更をリモートリポジトリのmainブランチに反映するとWorkflowが実行されます。 今度は成功することが確認できます。 ですが… TestFlightへの公開は行われていないようです。 アプリの暗号化書類について、予め選択していないと公開までは進みません。 今回は、独自のアルゴリズムで暗号化を行っていないので、Info.listに設定を追加して、TestFlightまで公開されるようにします。 App Uses Non-Exempt Encryption を追加して、値を「NO」にします。 変更をリポジトリのmainへ反映します。 DefaultのWorkflowを確認してみると、Buildは成功しています。 TestFlightの方も見てみると、公開までされていることが確認できます。 さいごに 以上で、Xcode Cloudの基本的な動作の体験は終わりになります。 お付き合いいただきありがとうございました。 全てのCI/CD設定がGUIで完了するのは分かりやすくとても良いと思いました。 今回の体験を基に複雑な処理など追加して、CI/CDをより使いやすくカスタマイズして、アプリ開発の効率を上げていきたいですね。(願望) リレーブログ企画「CI/CDリレーブログ」は、この記事で終了となります。執筆に協力していただいた皆さん、見てくださった皆さん、ありがとうございました。
アバター
この記事は、リレーブログ企画「CI/CD」の記事です。 はじめに おはようございます。IWSです。 今回は「CI/CDリレーブログ」ということで私のチームで使われている GitHub Actions を使った開発環境へのCDを紹介しようと思います。 イメージ この構成のWebアプリに対してCDする GitHub Actions WF を作成します。ECR にあるイメージを使ってECS タスクが動いているよくある構成ですね。 この構成に対して、developブランチにmergeされたときやGitHub Actionsのページから好きなブランチを指定して開発環境にデプロイできるようになるのがゴールです。 IAM Role の用意 WFを作成する前に、まずはAWSのリソースを操作するための権限周りの準備をします。 IAM Userでアクセスキーを取得して使う方法もありますが、IAM Role を使えば一時的なクレデンシャルを発行することができセキュリティ的にも管理のしやすさ的にも楽になるのでこちらがおすすめです。 ID プロバイダの作成 IAM の IDプロバイダ のページから作成していきます。 設定内容については GitHub Docs にも書かれていますが以下のように設定していけばOKです。 プロバイダのタイプ OpenID Connect プロバイダのURL https://token.actions.githubusercontent.com 対象者 sts.amazonaws.com IAM Role と Policy の作成 Policyを準備します。 操作したいリソースに合わせてActionの内容は変更してください。 今回はECSの更新やECRへのPushなどをするのでそのあたりを追加しています。 "sts:AssumeRoleWithWebIdentity" がないとWFが認証情報を受け取れないのでそこだけ注意。 { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ecs:UpdateService", "ecs:DescribeServices" ], "Resource": <ECS Sercice ARN> }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "elasticloadbalancing:RegisterTargets", "ecs:RegisterTaskDefinition", "elasticloadbalancing:DescribeTargetHealth", "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DeregisterTargets", "ecs:TagResource", "ecs:UntagResource", "ecs:DescribeTaskDefinition", "ecr:GetAuthorizationToken", "ecr:CompleteLayerUpload", "ecr:UploadLayerPart", "ecr:InitiateLayerUpload", "ecr:BatchCheckLayerAvailability", "ecr:PutImage", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "sts:AssumeRoleWithWebIdentity" ], "Resource": "*" } ] } Roleも作っていきましょう。 信頼されたエンティティタイプ ウェブアイデンティティ アイデンティティプロバイダー token.actions.githubusercontent.com Audience sts.amazonaws.com GitHub 組織、GitHub リポジトリ、GitHub ブランチ 自分のものを設定 ここで設定したリポジトリにあるWFにのみ認証情報を渡せるようになります。 次へ進んで先ほど作成した Policy を設定すればRoleの完成です。 ウェブアイデンティティで設定した内容は信頼ポリシーに反映されています。 Condition の StringLike の部分で Policy を渡してもいい相手を設定しています。↓の場合は test-repository 内の WF に対しては渡していいよ。という感じです。 { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRoleWithWebIdentity", "Principal": { "Federated": "arn:aws:iam::xxxxxxxxxxxxxxx:oidc-provider/token.actions.githubusercontent.com" }, "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": [ "sts.amazonaws.com" ] }, "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:test-user/test-repository:*" ] } } } ] } 間違っても "repo:*" みたいな設定はしないようにしましょう!どのリポジトリに対しても権限を渡してしまうゆるゆる設定になります! // すべてのユーザー、リポジトリに対しても権限を渡してしまう設定! "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:*" ] } ここまででIAM Roleを使ってWFがリソースの操作をできるようにする準備が終わりました! あとはWF側でこのRoleを受け取るだけですが - name: Configure AWS credentials from IAM Role uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} 公式のAction があるのでこれだけで受け取ることができます WF の作成 ここからはWFを作成します。 以下の通りに処理を行っていきます。 IAM Role の取得など初期準備 DockerfileのBuild, ECRへのPush ECSの更新 Slackへの通知 コード全体 先にコード全体を貼っておきます。 name: deploy ECS development on: workflow_dispatch: inputs: no-cache: description: "Build docker images with no cache" default: false required: false type: boolean push: branches: - develop env: AWS_REGION: "ap-northeast-1" ECR_REPOSITORY: "test-repository" ECS_SERVICE: "test-ecs-service" ECS_CLUSTER: "test-cluster" DESIRE_COUNT: "1" DOCKERFILE: "Dockerfile" SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} jobs: deploy: name: Deploy runs-on: ubuntu-latest timeout-minutes: 15 environment: development # GitHubのOIDCトークンエンドポイントとやり取りするために必要 permissions: id-token: write contents: read steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Configure AWS credentials from IAM Role uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 # image につけるタグを作成 # GITHUB_SHA の先頭7文字をタグにしています - name: Prepare IMAGE_TAG run: | IMAGE_TAG=$(echo ${GITHUB_SHA} | cut -c 1-7) echo "IMAGE_TAG=$(echo $IMAGE_TAG)" >> $GITHUB_ENV echo "build IMAGE_TAG: $IMAGE_TAG" - name: No Cache Option Check run: | # 指定した場合とpushでトリガーされた場合はキャッシュを無効にする if [ ${{ inputs.no-cache }} -o ${{ github.event_name }} -eq 'push' ]; then echo "NO_CACHE=true" >> $GITHUB_ENV else echo "NO_CACHE=false" >> $GITHUB_ENV fi # image を build し latest と github_sha 7桁をタグにして ECR に push - uses: docker/build-push-action@v6 id: build-image env: ECR_PATH: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }} with: file: ${{ env.DOCKERFILE }} push: true # ECRにpushするか tags: | ${{ env.ECR_PATH }}:${{ env.IMAGE_TAG }} ${{ env.ECR_PATH }}:latest cache-from: type=gha # キャッシュのソース, GitHub Actionsにあるのを使用 cache-to: type=gha,mode=max # キャッシュを GitHub Actionsに保管する。 max:中間ステップのすべてのレイヤーをエクスポート no-cache: ${{ env.NO_CACHE }} # キャッシュを使用するか provenance: false # Image indexを生成しない # --force-new-deployment で強制的にデプロイ - name: ECS Update id: update-ecs run: | # ECS更新 aws ecs update-service --cluster $ECS_CLUSTER --service $ECS_SERVICE --desired-count $DESIRE_COUNT --force-new-deployment # Slack通知 # 成功 - name: Slack Notification on Success if: ${{ success() }} uses: rtCamp/action-slack-notify@v2 env: SLACK_TITLE: development Deploy Success SLACK_COLOR: good # 失敗 - name: Slack Notification on Failure if: ${{ failure() }} uses: rtCamp/action-slack-notify@v2 env: SLACK_TITLE: development Deploy Failure SLACK_COLOR: danger トリガー、初期準備 トリガーにはdevelopブランチにpushされた時と手動実行を入れています。inputsについてはのちほど……。 envにはECRやECS指定のための値やIAM RoleのARNを、steps部分については初期準備として actions/checkout やECRにログインするための aws-actions/amazon-ecr-login などをしています。 name: deploy ECS development on: workflow_dispatch: inputs: no-cache: description: "Build docker images with no cache" default: false required: false type: boolean push: branches: - develop env: # 更新する ECS の Cluster,Service などの名前を指定 AWS_REGION: "ap-northeast-1" ECR_REPOSITORY: "test-repository" ECS_SERVICE: "test-ecs-service" ECS_CLUSTER: "test-cluster" DESIRE_COUNT: "1" # 開発環境のためタスク起動数は "1" DOCKERFILE: "Dockerfile" # build する Dockerfile を指定 SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} # 上記で作った IAM Role のARN jobs: deploy: name: Deploy runs-on: ubuntu-latest timeout-minutes: 15 environment: development permissions: id-token: write contents: read steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 # 権限を受け取る - name: Configure AWS credentials from IAM Role uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 imageのBuildとECRへのPush jobs: deploy: steps: # 〜省略〜 # image につけるタグを作成 # github sha の先頭7文字をタグにしています - name: Prepare IMAGE_TAG run: | IMAGE_TAG=$(echo ${GITHUB_SHA} | cut -c 1-7) echo "IMAGE_TAG=$(echo $IMAGE_TAG)" >> $GITHUB_ENV echo "build IMAGE_TAG: $IMAGE_TAG" - name: No Cache Option Check run: | # 指定した場合とpushでトリガーされた場合はキャッシュを無効にする if [ ${{ inputs.no-cache }} -o ${{ github.event_name }} -eq 'push' ]; then echo "NO_CACHE=true" >> $GITHUB_ENV else echo "NO_CACHE=false" >> $GITHUB_ENV fi # image を build し latest と github_sha 7桁をタグにして ECR に push - uses: docker/build-push-action@v6 id: build-image env: ECR_PATH: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }} with: file: ${{ env.DOCKERFILE }} push: true # ECRにpushするか tags: | ${{ env.ECR_PATH }}:${{ env.IMAGE_TAG }} ${{ env.ECR_PATH }}:latest cache-from: type=gha # キャッシュのソース, Github Actionsにあるのを使用 cache-to: type=gha,mode=max # キャッシュを Github Actionsに保管する。 max:中間ステップのすべてのレイヤーをエクスポート no-cache: ${{ env.NO_CACHE }} # キャッシュを使用するか provenance: false # Image indexを生成しない BuildとPushは docker/build-push-action を使ってやっています。このactionを使うだけでDockerfileのBuild、Push、キャッシュ保存までやってくれるためとりあえずこれを使っておけばいいと思います。 開発環境ですと確認のために何回もデプロイする……ということもあるのでキャッシュを使ってBuildにかかる時間を減らせるのはかなり助かります。そして面倒くさいキャッシュ設定が cache-from , cache-to だけで済むのもありがたいところですね。 キャッシュの使用は no-cache に bool で選べます。なので手動実行の際は inputs でチェックボックスを用意してチェックされたら使用しない。というふうにしています。 on: workflow_dispatch: inputs: no-cache: description: "Build docker images with no cache" default: false required: false type: boolean ECS の更新 開発環境用のWFなのでECSの更新も行っています。難しいことはしておらず AWS CLI で aws ecs update-service --force-new-deployment をして強制更新をかけているだけです。 使用するイメージについてはタスク定義で latest を使うようにしているため、更新をかけるだけで切り替わるようになっています。 # --force-new-deployment で強制的にデプロイ - name: ECS Update id: update-ecs run: | # ECS更新 aws ecs update-service --cluster $ECS_CLUSTER --service $ECS_SERVICE --desired-count $DESIRE_COUNT --force-new-deployment Slack通知 最後に action-slack-notify というactionを使ってWFが成功したかどうかを通知しています。GitHub Actionsは if: ${{ success() }} とするだけで 成功時/失敗時 の分岐ができるのが楽で好きです。 jobs: deploy: steps: # 〜省略〜 # Slack通知 # 成功 - name: Slack Notification on Success if: ${{ success() }} uses: rtCamp/action-slack-notify@v2 env: SLACK_TITLE: development Deploy Success SLACK_COLOR: good # 失敗 - name: Slack Notification on Failure if: ${{ failure() }} uses: rtCamp/action-slack-notify@v2 env: SLACK_TITLE: development Deploy Failure SLACK_COLOR: danger ちなみにここは Slack の GitHub App を使っても同じことができるのでやらなくても大丈夫です。お好きなやり方で通知してみてください。 まとめ GitHub ActionsでのCDについて書かせていただきました。 いままでは、いちいちAWSのコンソールから CodePipeline や CodeBuild などの設定を変更してから CodePipelineを実行、というふうにしていたのでこのWFが完成してからは格段に楽にデプロイできるようになりました。AWSにログインして設定をいじって実行なんて面倒くさいこと、いままでよくやっていたなと思います…… いまでも充分便利だなとは感じてはいますが、今度はGitHubのページを開くのが面倒くさいと思ってきたためSlackから発火できるようにできないかなと考えています。 もし完成したらこちらもまたブログにできたらと思っているので、その時はよろしくお願いします。 次回は、ブログ運営チームの投稿です。 おたのしみに!
アバター
ニフティキッズの開発担当をしている渡邊です。 3/24にニフティが運営する子ども向けサイト「ニフティキッズ」にて、AIイベントを開催しました。 当日は新宿本社に10組の親子を招き、AIについて学んでいただきました。 詳細に関しては PR TIMESの記事 をご確認ください。 今回は、ニフティキッズのマスコットキャラクターである「ひよりん」と会話ができるAIひよりんの裏側を話していきます。AIひよりんはイベント用に新しく作成しました。 システム構成 AIひよりんは、ユーザーが入力したテキストに対して「ひよりん」として応答したり、指示に基づいて画像を生成したりするWebアプリケーションです。返答されたテキストは音声合成により読み上げられます。 全体的なアーキテクチャは以下の要素で構成されています。 フロントエンド: ユーザーインターフェースを提供し、バックエンドAPIと通信。 バックエンドAPI: ビジネスロジックを処理し、外部サービス(生成AI、音声合成)と連携。 生成AI: テキスト生成と画像生成を行う(Amazon Bedrockを利用)。 音声合成: 生成されたテキストを音声に変換(VOICEVOXを利用)。 インフラ: アプリケーションの実行環境を提供(AWS App Runner, API Gateway, Lambda, S3などを利用)。 インフラ イベント開催まで限られた時間でしたので、柔軟な開発とスムーズなデプロイを実現できるアーキテクチャを目指しました。そのため、インフラ構築や管理のオーバーヘッドが少ないAWSのマネージドサービスを積極的に活用しました。 アプリケーション実行環境 バックエンドAPIサーバーやVOICEVOXサーバーのホスティングには、AWS App Runnerを選定しました。 App Runnerの詳細については、以前執筆したこちらの記事もご参照ください。 https://engineering.nifty.co.jp/blog/30738 APIエンドポイント フロントエンドからのリクエストを受け付けるAPIエンドポイントは、API GatewayとLambdaを用いてサーバーレス構成で構築しました。Lambda関数内で、後述するAmazon BedrockのAPIを呼び出しています。 画像ストレージ AIによって生成された画像は、Amazon S3に保存されます。保存後、フロントエンドで画像を表示するために、署名付きURLを発行する仕組みとしました。 音声入力 音声入力には、Google Cloudが提供するSpeech-to-Textを利用しました。このサービスはAPI経由で利用するため、インフラ管理は不要です。 生成AI テキスト生成と画像生成のコアとなるAI機能には、様々な基盤モデルをAPI経由で利用できるAmazon Bedrockを採用しました。 テキスト生成 (チャット) AIひよりんとの対話機能には、Anthropic社のClaude 3.7 Sonnetを利用しました。 開発当初、より高速なClaude 3.5 Haikuモデルも検証しましたが、AIひよりんのキャラクター設定(話し方、性格など)をプロンプトで指示した際に、Sonnetモデルの方がより忠実に、かつ自然な応答を生成することができたため、最終的にSonnetを採用しました。 画像生成 モデル間の品質比較を行うため、 Amazon Nova Canvas と Titan Image Generator G1 を選べるようにしています。 バックエンドAPI フロントエンドと各種AIサービス間のバックエンドAPIは、Go言語で実装しました。主な役割は以下の通りです。 フロントエンドからのリクエスト(対話テキスト、画像生成指示)を受け付ける。 リクエスト内容に基づき、Amazon BedrockのAPIを呼び出してテキスト生成や画像生成を実行する。 必要に応じてVOICEVOXサーバーにリクエストを送り、テキストの音声データを取得する。 生成されたテキスト、画像(のURL)、音声データをフロントエンドに返す。 APIとしては、主に以下の2種類を実装しました。 対話用API: ユーザー入力と会話履歴を受け取り、Bedrock (Claude) で生成された応答テキストと、VOICEVOXで生成された音声データを返す。 画像生成用API: 画像生成指示のテキストを受け取り、Bedrockで生成された画像のS3 URLを返す。 Go言語を選定した理由は実行速度の速さ、静的型付けによる堅牢性、並行処理の容易さなどです。 音声合成 AIひよりんが生成したテキストメッセージを、よりキャラクターらしく自然な音声で読み上げるために、オープンソースの高品質な音声合成ソフトウェアであるVOICEVOXを利用しました。 今回はDockerコンテナ版のVOICEVOXを採用し、バックエンドAPIサーバーと同様にApp Runner上でホスティングしました。 これにより、音声合成機能を独立したマイクロサービスとして運用でき、インフラ管理の負担を軽減しつつ、スケーラビリティも確保することができました。 バックエンドAPIは、このVOICEVOXサーバーに対してHTTPリクエストを送ることで音声データを取得します。 フロントエンド Next.jsを用いて構築しました。主な機能は以下の通りです。 ユーザーがAIひよりんへのメッセージを入力するテキストボックス。 AIひよりんからの応答テキストの表示。 生成された画像の表示。 音声を再生する機能。 バックエンドAPIとの非同期通信(対話、画像生成リクエスト)。 ブラウザのMediaRecorder APIを利用した音声録音・データ取得処理。 出来上がったもの チャット 画像生成 まとめ 今回のAIひよりん開発プロジェクトでは、Amazon Bedrockを中心としたAWSのマネージドサービスと、VOICEVOXのようなオープンソースソフトウェアを組み合わせることで、短期間で子どもたちに楽しんでもらえるAIアプリケーションを実現することができました。 特に、App RunnerやLambda、API Gatewayといったサービスを活用することで、インフラ構築・管理の工数を大幅に削減し、アプリケーションロジックとAI連携部分の開発に集中できたことが、迅速なリリースにつながったと考えています。
アバター
はじめに こんにちは。ニフティ株式会社の西原です。 今回はRancher Desktopについて紹介します。 これからDocker Desktopの代替でRancher Desktopを導入される方の参考になれば幸いです。 背景 Docker Desktop値上がりによる代替ツール探しの話。 弊社のマネージャ―が Rancher Desktopよさそうということを周知してくれたのでWSL2の環境にインストールすることに。 WSL2って? Windows Subsystem for Linux (WSL) は Windows の機能であり、別の仮想マシンやデュアル ブートを必要とせずに、Windows マシンで Linux 環境を実行できます。  https://learn.microsoft.com/ja-jp/windows/wsl/about https://learn.microsoft.com/ja-jp/windows/wsl/install Macユーザーからの認知が少なそうなので一応説明しておくと、Windows上でLinux動かせるんですよ。 「あいつら .msiファイルダウンロードしないと何もインストール出来ないんだぜ、TeraTermとか落として来ないとどこかのサーバにSSHすることもできないんだぜ」 とか思ってるMac派の人は認識を改めてください。 インストールで躓いたところ 最初にDocker Desktopをアンインストールしておくべきだった。 よくわからなくなって、以下の流れで導入してしまい、躓いてしまった。 Rancher Desktopをインストール Docker Desktopをアンインストール 動きが変になってRancher Desktop をリペアインストール 公式でもDocker Desktop消してからやれと書かれていた気がする。 docker compose でエラーになる docker composeコマンド実行したら以下のようになった $ docker compose up -d docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory docker-credential-secretservice: error while loading shared libraries: libsecret-1.so.0: cannot open shared object file: No such file or directory 以下で対処した sudo apt install libsecret-1-dev イメージのpullでエラーになる 上記を実行したあと docker compose up したら今度は以下のエラーが出た $ docker compose up -d [+] Running 0/0 ⠋ django Pulling 0.0s ⠋ minio Pulling 0.0s ⠋ createbuckets Pulling 0.0s ⠋ wiremock Pulling 0.0s ⠋ mock Pulling 0.0s error getting credentials - err: exit status 1, out: `Could not connect: No such file or directory` error getting credentials – err: exit status 1, out: Could not connect: No such file or directory ~/.docker/config.json に以下を追加 "credsStore": "pass" 現在の ~/.docker/config.json の状態が以下 { "cliPluginsExtraDirs":["/mnt/c/Users/UsersName/AppData/Local/Programs/Rancher Desktop/resources/resources/linux/docker-cli-plugins"], "credsStore": "pass" } 無事使えるようになりました。 Rancher Desktopの使用感について まだしっかり利用できていないですが、今まで使っていた docker のコマンドがそのまま使えるので違和感なく利用できています。 おわりに 今回はRancher Desktopについて紹介しました。 WSL2でRancher Desktopを導入する際に参考になれば幸いです。
アバター
はじめに こんにちは。ニフティ株式会社の山田です。 今回はAWS Lambdaのログレベルが意図せず無効化されていた事象に遭遇したので、その体験について紹介します。 概要 AWS Lambdaの基盤ログをJSON形式で出力できるようになったため、設定を変更したら意図せずログレベルが無効になっていました。 事象 前提 Lambda PowertoolsのLoggerでログを出している POWERTOOLS_LOG_LEVEL 環境変数で出力ログレベルを制御している 起こったこと AWS LambdaのログはAWS CloudWatch Logsに流れるので、JSON形式で出力することが一般的です。 ところがLambda基盤が出すstart、stopなどのログはプレーンテキスト形式で固定されており、構造化されていないため検索が困難でした。 ここが2023/11のアップデートで変更され、JSON形式で出力できるようになりました。 AWS Lambda の高度なログ制御機能のご紹介 | Amazon Web Services というわけでCDKから PythonFunction( ... logging_format=LoggingFormat.JSON, ) と設定したところ、 環境変数によるログレベル設定が効かなくなり、INFOレベル固定となってしまいました 。 (元々WARNINGに設定されており、INFOになったことでAWS CloudWatch Logsの料金が跳ね上がり発覚しました) 原因 AWSコンソールから見るとわかりやすいのですが、JSONへの変更はただフォーマットを変えるだけではなく、 Advanced Logging Control(ALC)を有効にすることを意味します 。そして、 ALCにはログレベルの制御が含まれていました 。 AWSコンソールから見たロギング設定 AWSコンソールから見たロギング設定 AWSコンソールから見たロギング設定 問題となるのはアプリケーションログレベルで、この設定により 標準ロガー(Pythonだとlogging.Logger)の出力ログレベルを設定値で制限する Logger側がINFOでも、ALC側がWARNならWARN以上しか出力されない AWS_LAMBDA_LOG_LEVEL 環境変数にログレベルを設定する という動作になります。 さらにLambda Powertoolsは AWS_LAMBDA_LOG_LEVEL 環境変数を最優先のログレベル設定として取り扱います。 Logger – Powertools for AWS Lambda (Python) 結果として、ALCのログレベルしか参照されておらず、 POWERTOOLS_LOG_LEVEL 環境変数は機能しない状態となっていました。 対策 POWERTOOLS_LOG_LEVEL 環境変数をやめ、ALCによる設定に変更しました。 PythonFunction( ... logging_format=LoggingFormat.JSON, + application_log_level_v2=ApplicationLogLevel.WARN, - environment={ - "POWERTOOLS_LOG_LEVEL": "WARNING", - } ) まとめ 新しい設定を導入するときは、設定される内容の範囲を確認するようにしましょう。
アバター
この記事は、リレーブログ企画「CI/CD」の記事です。 はじめに ニフティでWEBサービスの開発・運用を担当している渡邊です。 Goで実装した複数のバッチをAWS Lambdaにデプロイする機会があったので、そのときに実装した内容を紹介していきます。 構成 GitHub Actionsを使ってデプロイを実装しています。 Lambdaはコンテナベースでバッチを管理し、GitHub ActionsによってECRへプッシュします。 通常、LambdaをECRのプッシュをトリガーに自動更新すにはEventBridgeが必要ですが、今回は管理を簡素化するため、Lambdaの更新もGitHub Actionsで直接行うことにしました。 ディレクトリ構成 以下のようなディレクトリ構造でバッチを実装することを想定します。 cmd/ディレクトリの下に、各バッチのメインロジックがそれぞれのディレクトリに配置される構成です。 各バッチディレクトリにDockerfileを設置し、それぞれのロジックが独立したLambda(バッチ)として実行されます。 .github内にデプロイを行うワークフローを配置しています。 .github └── workflows ├── deploy_batch1.yml ├── deploy_batch2.yml ├── deploy_batch3.yml ├── reusable_build_push_docker_image.yml ├── reusable_update_lambda_function_image.yml batch ├── cmd │ ├── batch1 │ │ └── main.go │ │ └── Dockerfile │ ├── batch2 │ │ └── main.go │ │ └── Dockerfile │ └── batch3 │ └── main.go │ └── Dockerfile ├── go.mod ├── go.sum ├── models └── hoge.go └── usecase └── hoge.go Dockerfileの中身 FROM --platform=$BUILDPLATFORM golang:1.24.0-alpine3.21 AS builder ARG TARGETARCH WORKDIR / COPY ./ /. WORKDIR /cmd/batch1 RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -o batch1 ./main.go FROM alpine:3.21 RUN apk --no-cache add ca-certificates COPY --from=builder /cmd/batch1 . COPY /lambda_layer/ /opt/ ENTRYPOINT [ "/batch1" ] ワークフロー 今回は例として3つのバッチを作成するようになっていますが、実際に運用するときは数十個以上のバッチを管理することもあります。そのため、GitHub Actionsのワークフローは再利用できるように作っていきます。 さらに、今回の構成では Arm64 向けにビルドすることで、実行環境でのパフォーマンス向上を目指します。 ワークフローを作る前に、リポジトリに以下の設定が必要です。 AWS_ACCESS_IAM_ROLE AWSへのアクセスをOIDCで行うため 詳細は こちら AWS_REGION ECRとLambdaをデプロイするリージョン AWS_ECR_REGISTRY_URI ECRのレジストリURI まず、ECRへイメージをプッシュするワークフローを作ります。 ARM64で動くバッチにするための方法を2パターン紹介します。 ビルド Linux x86_64で動かす場合 GitHub Actions のデフォルトの実行環境は Linux x86_64 です。そのため、Arm64 向けにビルドを行うには クロスコンパイルが必要になります。 Go の場合、公式にクロスコンパイルがサポートされているため、環境変数を指定することで比較的簡単に Arm64 向けのバイナリを作成できます。 また、Docker イメージのように OS/アーキテクチャが密接に関係する場合には、QEMU(マルチプラットフォームイメージのビルド)を用いたエミュレーションとDocker Buildxを使ってマルチアーキテクチャ対応のビルドを行う方法が有効です。 以下がワークフローの処理になります。 # reusable_build_push_docker_image.yml on: workflow_call: inputs: docker_image_name: # ECRリポジトリ名 required: true type: string docker_tag: # イメージタグ(通常はlatest) required: true type: string dockerfile_path: # Dockerfileのパス required: true type: string secrets: AWS_ACCESS_IAM_ROLE: # AWS IAMロール required: true jobs: build_push_docker_image: name: Build and push Docker image runs-on: ubuntu-latest timeout-minutes: 3 environment: ${{ inputs.environment }} permissions: id-token: write # OIDC認証用 contents: read # リポジトリ読み取り用 steps: - name: Check out code uses: actions/checkout@v4 - name: Setup QEMU(linux/arm64) uses: docker/setup-qemu-action@v3 with: platforms: linux/arm64 - name: Build Docker image run: docker buildx build --platform linux/arm64 -t ${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} . -f ${{ inputs.dockerfile_path }} - name: Assume role AWS token uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ vars.AWS_ACCESS_IAM_ROLE }} aws-region: ${{ vars.AWS_REGION }} - name: Push Docker image to ECR run: | aws ecr get-login-password --region ${{ vars.AWS_REGION }} | docker login --username AWS --password-stdin ${{ vars.AWS_ECR_REGISTRY_URI }} docker tag ${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} docker push ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} メリット GitHub Actionsの毎月の無料利用枠内で実行できるため、コストを抑えられる デメリット QEMUを使用してArm64環境をエミュレートするため、ネイティブのArm64環境でのビルドと比べて処理が遅くなる Arm64ランナーを使う場合 2024/06/03にGitHub ActionsにArm64ランナーが追加されました。 そのため、QEMUが不要になり、直接ビルドを行えるようになりました。 以下がQEMUを使わないワークフローになります。 パブリックリポジトリの場合は ubuntu-24.04-arm を、プライベートリポジトリの場合はArm64のランナーを作成して指定します。 # reusable_build_push_docker_image.yml on: workflow_call: input docker_image_name: # ECRリポジトリ名 required: true type: string docker_tag: # イメージタグ(通常はlatest) required: true type: string dockerfile_path: # Dockerfileのパス required: true type: string aws_access_iam_role: # AWS IAMロール required: true type: string jobs: build_push_docker_image: name: Build and push Docker image runs-on: ubuntu-24.04-arm timeout-minutes: 3 environment: ${{ inputs.environment }} permissions: id-token: write # OIDC認証用 contents: read # リポジトリ読み取り用 steps: - name: Check out code uses: actions/checkout@v4 - name: Build Docker image run: docker buildx build --platform linux/arm64 -t ${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} . -f ${{ inputs.dockerfile_path }} - name: Assume role AWS token uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ vars.AWS_ACCESS_IAM_ROLE }} aws-region: ${{ vars.AWS_REGION }} - name: Push Docker image to ECR run: | aws ecr get-login-password --region ${{ vars.AWS_REGION }} | docker login --username AWS --password-stdin ${{ vars.AWS_ECR_REGISTRY_URI }} docker tag ${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} docker push ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} メリット QEMUを使っていないため、より高速にビルドを行える デメリット プライベートリポジトリではLarge Runner扱いのため、無料利用枠外の扱いになってしまう(2025/04/02現在) Lambdaへのデプロイ aws cliを使ってLambdaファンクションを更新します。 # reusable_update_lambda_function_image.yml on: workflow_call: inputs: docker_image_name: # ECRリポジトリ名 required: true type: string docker_tag: # イメージタグ(通常はlatest) required: true type: string lambda_function_name: # Lambdaファンクション名 required: true type: string aws_access_iam_role: # AWS IAMロール required: true type: string jobs: updete_lambda_function_image: name: Updete Lambda function image runs-on: ubuntu-latest timeout-minutes: 3 environment: ${{ inputs.environment }} permissions: id-token: write contents: read steps: - name: Assume role AWS token uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ vars.AWS_ACCESS_IAM_ROLE }} aws-region: ${{ vars.AWS_REGION }} - name: Updete Lambda function image run: aws lambda update-function-code --region ${{ vars.AWS_REGION }} --function-name ${{ inputs.lambda_function_name }} --architectures arm64 --image ${{ vars.AWS_ECR_REGISTRY_URI }}/${{ inputs.docker_image_name }}:${{ inputs.docker_tag }} メイン処理 先ほど作成した再利用可能なワークフローを呼び出します。 Dockerのイメージ名とファイルパスを指定し、各バッチごとにこの設定を作成することでデプロイ環境が完成します。 # deploy_batch1.yml name: Deploy batch1 on: push: branches: - develop paths: - "cmd/batch1/**" - "models/**" - "usecase/**" - "repository/**" - "go.mod" - "go.sum" jobs: build_push_docker_image: name: Call reusable build and push Docker image workflow uses: ./.github/workflows/reusable_build_push_docker_image.yml with: docker_image_name: batch1-ecr docker_tag: latest dockerfile_path: cmd/batch1/Dockerfile aws_access_iam_role: ${{ vars.AWS_ACCESS_IAM_ROLE }} update_lambda_function_ime: name: Call reusable update Lambda function image needs: [build_push_docker_image] uses: ./.github/workflows/reusable_update_lambda_function_image.yml with: docker_tag: latest lambda_function_name: batch1 docker_image_name: batch1-ecr aws_access_iam_role: ${{ vars.AWS_ACCESS_IAM_ROLE }} 実行 今回は特定ブランチへのpushをトリガーとしてワークフローが実行されるように設定しました。 以下が、実際にデプロイした際の実行結果です。 QEMUを使った場合 Arm64ランナーを使った場合 検証に使ったバッチでは、Arm64ランナーを使うことで処理が少し速くなりました。 コードの大きさによってはもっと大きな差が出てくると思います。早くプライベートリポジトリでも無料利用枠ないで使えるようになるといいですね。 最後に 今回はGoで作成したバッチをLambdaに効率よくデプロイする方法を紹介しました。 GitHub Actionsは再利用可能なワークフローを作成することができるので、今回のようなデプロイ先が違うだけで、内部処理が一緒の場合は非常に役立ちます。 また、今回のように複数のバッチをコンテナベースで管理することで、環境の統一性が保たれ、メンテナンス性も向上します。 次回は、IWS さんです。 今回と同じくGitHub Actionsを使ったデプロイ方法を紹介してくれるようなので、楽しみです!
アバター
新しい技術の導入を含め、エンジニアのチャレンジを後押ししてくれる あらためて、みなさんが思うニフティという会社の「いいところ」を教えてください。 A.Mさん エンジニア視点だと、色々なことに挑戦できる会社だと思います。会社によっては完全な分業制で、サービスのなかの一部分の開発しか任せられないケースもありますが、ニフティの場合は希望すればサービス全体に携われることも珍しくありません。現に「マイ ニフティ」でも、当初こそiOS版の開発だけでしたが、今ではAndroid版やその裏側の仕組みも含めて全てを任せてもらえています。もちろん大変さはありますが、フルスタックにスキルを習得できるのは、エンジニアにとって大きなメリットではないでしょうか。 また、労働環境の面では残業時間も多くはないですし、働きやすい会社だと思います。もちろん配属部署や業務内容、その時々の仕事の状況によっても変わってきますが、有給休暇も2日前の申請で問題なく取れたりと、色々と柔軟に対応してもらえますね。 M.Kさん A.Mの話と少し似ていますが、色んな技術を試せるところがニフティの良さだと思います。最初から特定の技術ありきではなく、その都度、サービスに合った技術を検討しながら開発を進める文化が当たり前に根付いている。それまで社内で採用されたことのない新しい技術でも、それを選んだ妥当性を説明できれば、問題なく採用してもらえます。そうした意味でも、エンジニアがチャレンジしやすい環境なのかなと。また、チャレンジという意味では僕が利用した公募制度もそうですね。自分がより成長するために、希望するポジションに異動できる制度があるのは本当に魅力的だと思います。 K.Nさん チャレンジのしやすさは、私も感じます。私自身も、新卒時はデザインチームのコーダーとして配属され、WEBサイトのディレクションを担当していました。そこからさまざまな経験を積むなかで徐々に現在のスクラムエバンジェリストにシフトしていったのですが、そのために上司や会社への説得・交渉なども特に必要なくて。「私はこっちの方向に進みたいから、こんな資格を取ります」と説明すれば、基本的には認めてもらえます。「あれをやるな」みたいなことを言われた経験もほとんどないですし、かなり現場に任されているところはありますね。 また、会社の制度では「N1!制度」といって、特定の分野で突出した知識とスキルを持った社員を任命し、1年間100万円の活動資金を割り当てる仕組みもあります。活動内容は担当業務の改善や、社内における専門分野・技術の提唱、社外活動への参加などさまざまで、各自の専門性をさらに磨くことができるんです。私自身、この制度を活用してスクラムエバンジェリストになり、現在では社内外にスクラムの良さを広める活動を行っています。 K.Nさん以外にも、社内には主体的に新しいスキルや知識を身につけようとする人が多いのでしょうか? K.Nさん そう思います。たとえば勉強会をやろうと呼びかけると、毎回2〜3人は必ず「参加したい」と手を挙げてくれます。全体的に、みんなで一緒に学んでいこうという姿勢が感じられる会社だと思います。 M.Kさん 確かに、勉強会は活発に行われていますよね。新しい技術や、社内の知られざる技術みたいなものについて、誰かが勉強会をやろうと提案するとすぐにメンバーが集まる感覚があります。業務時間内で勉強会を行うことも許してくれるなど会社の後押しもあるので、知識を獲得する、スキルを磨く上ではとても恵まれた環境だと思います。 社内にはさまざまなモデルケースも。 若手エンジニアのキャリアパスは? 最後に、みなさんのキャリアパスについて伺います。A.Mさん、M.Kさんは新卒入社から6年目です。次のキャリア、将来のキャリアについても真剣に考える頃合いかと思いますが、具体的に描いているビジョンなどはありますか? A.Mさん 正直、そこはまだ悩み中ですね。このまま開発者として突き進んでいくのか、マネジメント寄りの方向性にシフトしていくのか。社内にはどちらのタイプの先輩もいるので、色んな話を聞いて参考にしながら、自分に合った道を選んでいきたいですね。上司との1on1など、キャリアについて相談したり、掘り下げて考えられる場もあるので、それらもうまく活用しながら自分の思いを明確にしていきたいと考えています。 M.Kさん 僕も同じく、悩んでいます。入社当初はそれこそ「コードを書きまくるぞ」みたいな感じでしたが、最近は徐々にマネジメントの役割を求められる場面も増えてきました。A.Mが言うように、マネジメントと現場での開発を自分のなかでどうバランスしていくか。あるいは、社内には幅広い知識を活かして、色んなチームを渡り歩いてサポートするような人もいて、本当に色々な道があるなと。そこは、色んな人の動きを参考にしながら見定めていきたいですね。 前編もご覧ください! 今回はニフティのマイ ニフティチームのインタビューの様子をお届けしました。あわせて前編もご覧ください。 【インタビュー】会社の顔に等しいサービスを手掛ける重圧。「マイ ニフティ」の開発で感じたエンジニアとしての成長【マイ ニフティ前編】 ニフティでは、さまざまなプロダクトへ挑戦するエンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトよりお気軽にご連絡ください! このインタビューに関する求人情報 /ブログ記事 ニフティ株式会社 求人情報
アバター
入社してから経験してきたことについて こんにちは!2023年にニフティへ中途入社しました、好田(よしだ)と申します。 入社してから2年目に突入しております。現在はインフラグループとして主にカスタマーサポートで使用している環境の運用を行っています。 前職からもカスタマーサポートで利用している環境の運用を行っていたため、インフラ業務に配属させていただきました。まずは前職で行ってきたことについて書いていこうかと思います。 前職で行っていたこと 前職ではオペレーター業務・ニフティのシステムの監視・運用業務を行っておりました。 オペレータ時代の業務をざっくりと記載しますと、以下の通りです。 ・システムの監視 ・媒体交換作業 ・機器ランプの定期目視 所属していたオペレーターの仕事は、案件別に手順書を使用し、状況に応じた特定の手順に従って対応を行っていきます。手順例外時は担当者へ連絡を行う形式です。監視するシステムの種類や、アラートの内容、メールや電話での問い合わせ対応、媒体交換など、それぞれの状況に応じた手順が記載されており、その数は最大で約100種類の案件、対応方法が記載されていました。 運用に携わるようになってからは、オペレータ向けの手順の作成なども実施しましたが、運用に関わる担当者によって作成する手順の構成が異なっていることが原因でオペレータが確認し辛くなってしまうという事がありました。そこでフローを作成する場合や、手順の記載を統一化する必要性を学びました。 監視ツールとしては、HPOMが中心で、Zabbix、AS400、datadog、Windowsメールなども使用していました。ジョブ管理ツールは、A-AUTOやJP1などがメインでした。 その中の対応で異常アラートを検知した場合は手順に従った対応、電話連絡等の対応を実施していました。正常アラートについても特定の時間帯に問題なく出力されているかを確認しています。 オペレーションミスの対策 作業や対応を行っていると実施漏れや、対応ミス等が発生することがあります。その対策ついて話し合うのが「なぜなぜ分析」でした。元々は某企業で行っているとされています。 この「なぜなぜ分析」について簡潔に説明しますと、発生した事象や、実際に行った行動にそれぞれ「なぜ?」という問いかけを繰り返すことで、問題の根本的な原因を追求していくのが特徴で、目的としては、問題の根本原因を特定し、解決、品質向上を目指すといったものですので、実施するのにあたっては対策まで進めていくのには時間を要しますが、再発防止策などの検討には役立つかもしれません。 ニフティに入社して 入社してからは、主にサポートVDI環境におけるDaasの環境からオンプレミス、構築・設計・運用を行ってきました。現在移行中の環境もありますが、随時対応を行っております。 成長したと思う事 運用業務がメインであったため、構築に関わる機会がありませんでしたが、サポートVDI環境リプレイスを担当させていただいたことで、構築を行った事や、今まででは経験してこなかった事を経験できたことは今後にも活かせるのではないかと思っています。 属人化解消に向けて 属人化はいつの時でも出てくる課題になるかとは思います。気にしたうえで、設計及び、詳細設定にインストールしたアプリの設定詳細、データを残しておいていたとしても、きちんと内部で共有しておかないと、今後対応に携わった人が元の設定は何だったのか不明の状況が出てくるかと思います。なかなか構築を行いつつ、文書化しつつ共有を行っていく事は大変かと思いますが、どのような設定を行ったのか、インストール実行時におけるパラメータの設定は何を設定したのか詳細に記しておかなかければ、構築時はある程度記憶しているものですが、当時はどうだったのか、時間が経つにつれ朧げになっていくものかと思います。そのためには仕様だけではなく、詳細に設定した項目もきちんと残しておかなければならないと考えています。前職の経験を活かし、ドキュメントも統一性をもって作成することで、誰が見ても分かりやすい対応手順の作成を心がけを行い、チームのメンバーとは定期的に対応した内容や、設定項目の共有を行い、属人化解消に向けドキュメントの整備を続けています。 最後に 前職では運用の業務や、オペレーションを行っていましたが、ニフティに入社してからは、ドキュメント以外にも構築の分野であったり、リプレイス対応や新たな業務にも挑戦しているので、まだまだ未熟なところは多々ありますが、その中で新たな学びを得ることで今後も成長出来たらと思います。 ミス対策のなぜなぜ分析については結構有名なものですので、ご興味のある方は情報も沢山ありますので、調べていただくと良いかなと思います。
アバター
はじめに 会員システムグループのkiqkiqです。 自チームではawsのインフラはTerraformで管理しています。Terraformはインフラストラクチャをコードとして管理する便利なツールですがその便利さの中にも注意すべき内容もあります。今回はリソース作成時に遭遇した、予期せぬリソースの上書きについて共有したいと思います。 遭遇した問題 自チームでは同一AWSアカウント内で複数のTerraformプロジェクトを利用しており、気付かず同じ名前のEventBridge RuleをTerraformで定義してしまいました。その状況で同一名称のEventBridge Ruleをデプロイしたとき、エラーが発生するのではなく元からあったリソースの内容を上書きするという挙動に遭遇しました。 例えば、以下のようなaws_cloudwatch_log_groupのリソースを作成した場合、同じ /example/example_logs というロググループのリソースがあると terraform apply 時にエラーとして出力されます。 resource "aws_cloudwatch_log_group" "example_logs" { name = "/example/example_logs" # ... その他の設定 ... } しかし、以下のようなaws_cloudwatch_event_ruleのリソースを作成した場合、 example-rule というEvent Ruleのリソースがすでに同アカウント内に存在しても上書きされてしまいます。 resource "aws_cloudwatch_event_rule" "example_rule" { name = "example-rule" description = "新しいルール" # ... その他の設定 ... } 同一Terraformプロジェクト内で定義している場合は、plan実行時に Error: Duplicate resource "aws_cloudwatch_event_rule" configuration というエラーが発生します。しかし、今回のように別プロジェクトで管理してるとterraform plan時の差分では確認できず、 terraform apply でもエラーが発生せずにデプロイできてしまいます。 予防策の例としては以下のようなものが挙げられます。 同一アカウント内で複数のTerraformプロジェクトを利用している場合はプロジェクト全体での命名規則を統一しプレフィックスなどで分ける 名前をあえて明示的に指定せず自動採番に任せる それぞれメリットデメリットがあり、他にも方法はいくらでもあると思うので状況に応じて適切なものを取り入れてみてください。 まとめ このようにリソース名の重複がある場合、大半のリソースは terraform apply 実行時にエラーとして出力してくれますが一部のリソースではエラーにならず既存リソースが更新される場合があります。今回の場合はTerraformプロジェクトが分かれていたということもあり、既存リソースが上書きされてしまっていることに気付くのが遅れました。特にEvent Ruleは定期実行されるまで気づけず障害になる可能性もあるため気をつけてください。 備考 関連issue:  https://github.com/hashicorp/terraform-provider-aws/issues/25598
アバター
はじめに 基幹システムグループの島です。この記事ではさほどAIに詳しくない私がRoo Codeを使って実際にコーディングしてみた体験を書きます。 Roo Codeは簡単に使えるのでAIの利用に不慣れな人にオススメだと感じました。そういった人の参考になればと思います。 ※ Roo Codeについては公式のリポジトリを参照ください 1 この方法はGitHub Copilotを利用可能な状態になっていることが前提条件です インストール VSCodeの拡張機能マーケットプレイスから「Roo Code」と検索するとRoo Codeが出てくるのでインストールします。 拡張機能からRoo Codeをインストール インストールすると左バーにロケットのアイコンが表示されるので、そこからRoo Codeを利用できます。 ロケットのアイコンをクリック 以上でインストールは完了です。次に初期設定を行います。 初期設定 利用するモデルを設定する はじめに利用するAPIプロバイダーと言語モデルを設定します。 API Providerは「 VS Code LM API 」を選択します。これはGitHub Copilotが提供しているAPIで、GitHub Copilotが使える状態であれば追加課金も発生せず、細かい設定も不要で気軽に使えます。 Language Modelはお好みのモデルを選んでください。今回は「 copilot -claude-3.5-sonnet 」にしました。 APIプロバイダーと言語モデルの設定 権限を設定する 次にRoo Codeの権限を設定します。画面下部の「Auto-approve」と書かれている部分をクリックすると権限設定を表示できます。 権限設定の開き方 権限設定の開き方 権限設定の開き方 Auto-approveにチェックをつけると承認ボタンを押さなくても自動でコードを読んで変更してくれます。 私はRoo Codeがどのコードを読んでどういう変更をしたかを理解しながら進めたかったのでAuto-approveはオフにして、承認しながら進めるようにしました。 権限はコードを読んで変更してもらうだけなら「Read files and directories」と「Edit fles」にチェックを付けておけば問題ないです。 権限設定 権限設定 権限設定 以上でRoo Codeを利用する準備ができました。ここからは実際にRoo Codeを使ってコーディングしていきます。 実際にコーディングしてみる 下部の入力欄にプロンプトを入力します。今回入力したプロンプトは以下です。 slack.rsの352行で投稿数が多いとSlack APIのrate limitエラーになってしまう。一度ユーザー情報を取得したユーザーについてはSlackAPIを呼ばずにユーザー名を設定するように変更し、APIの呼び出し回数を減らしたい プロンプトを入力 プロンプトを入力 プロンプトを入力 もっとざっくりしたプロンプトでも動きますが詳しく書いた方が精度は上がります。 プロンプトを送るとslack.rsを読みたいと返ってきました。Approveを押して承認します。 プロンプトへのレスポンス プロンプトへのレスポンス プロンプトへのレスポンス コードの問題点と変更内容の説明が提示されます。あわせて変更したコードも提示されます。Saveを押すと変更したコードが保存されます。Rejectを押したり、追加で別の指示を送ると別の修正案を提示してくれます。 変更したソースコードとその説明が表示される 変更したソースコードとその説明が表示される 変更したソースコードとその説明が表示される Saveすると次の修正箇所が提示されます。これも確認して問題なければSaveします。 次の修正コードが提示される 次の修正コードが提示される 次の修正コードが提示される これを続けていってすべての修正が完了するとTask Completedとなり終了します。 すべての修正が終わるとTask Completedとなり終了する 修正されたソースです。数行の修正であれば、確認しながらでも1~2分で終わります。複数ファイルの修正ももちろんしてくれます。 所感 1回のプロンプトで変更箇所の特定から複数のコード修正までやってくれるので使いやすかったです。また日本語に対応しているのも嬉しいポイントです。 自分は今後以下のような使い方をしようと考えています。 自分で変更したコードの答え合わせやより良い変更案を提案してもらう 最初にRoo Codeにコード変更してもらい、それをベースに自分で微調整する 毎回完璧なコードを書けるわけではないので、人の目によるチェックは必要です。 余談ですが最近はGitHub CopilotがGitHub上でプルリクエストのレビューもしてくれるのでAIに書かせてAIにレビューしてもらうこともできます。 GitHub Copilotによるプルリクエストのコードレビュー 簡単に使えるのでぜひ使ってみてください。 脚注 Roo-Code: https://github.com/RooVetGit/Roo-Code ︎
アバター
Cloudflare管理者をしている石川です。 先日開催されたCloudflare Meet-upで「Cloudflare Streamを使った簡単動画配信」というタイトルでLTしてきました。 Cloudflare Meet-up Tokyo Vol.7 – connpass 個人的にCloudflareは2年ほど前から使ってましたが、会社としては今回が初導入となります。前々からCloudflare使いたいなというシーンはあったのですが、今回ちょうどいい内容とタイミングでしたので導入と相成りました。 導入後にまず考慮するのは構成管理ですが、CloudflareはTerraformサポートしています。 現状アカウント管理だけIaC化して運用しています(Streamはコンテンツ管理しかないので構成は不要) 参考: Overview · Cloudflare Terraform docs WorkersやPagesを使っているときにも感じていましたが、複雑なことをしなければCloudflareはかなり使いやすいプラットフォームだと思います。Streamのその例に漏れず動画配信環境を簡単に作れますし、費用見積もりもしやすかったのが今回とても助かりました。 他の方のLTで気になったこと PagesとWorkers統合の気配 前々からちょいちょい確度の高い噂であったWorkersへの統合。 参考: 最近のCloudflare Workers – ゆーすけべー日記 Pagesなくなるとそこそこ困るんですが、お互いのいいところどりの形で統合してほしいですね。 4月か5月に行われるであろうDeveloper Weekで発表される可能性もあるので要チェックです。 参考: Developer Week | Cloudflare Cloudflareのざっくりとした権限 Cloudflareでアカウント管理し出してから私も知ったのですが、結構ざっくりとしたRoleしか用意されていません。 参考: Account roles · Cloudflare Fundamentals docs Pagesでビルドログだけ見せたいのに、WorkersのAdmin権限を与えないといけないなど、過剰権限で運用を強いられるシーンもあり、今後の組織での利用でちょっと頭を悩ませている点です。 このあたりも今後アップデートがあると嬉しいですね。
アバター
ニフティ株式会社の仲上です。以前Notionでの作図について記事を書きました。 【2023年12月版】Notionの作図機能紹介(会社編) 今回はdraw.io(Diagrams.net)に特化したお話をします。Notionでdraw.ioを使った作図をして困っている方は参考にしてみてください。 はじめに 設計とか要件整理とかしていると、よくdraw.ioで図を書くことが増えます。 ただ、そういった画像をpngやjpegで貼り付けると、あとから編集できなくて困ることがあります(編集元のファイルはマイドライブの闇の中へ……)。 そんな悩みの解決方法について話していきます。 その1 SVGでエクスポートするべし draw.ioはSVG形式でも読み込むことができます。 SVGは汎用的なファイル形式でどこでも使うことができるので、基本この形式で保存することをおすすめします(生のdraw.ioは独自形式のXMLファイル)。 その2 Notionにペーストすべし SVGにエクスポートしても、共有できる場所にはっておかなければ意味がありません。 Notionに貼って、誰でも見れるようにしましょう。 その3 draw.ioの拡張機能をいれるべし ぶっちゃけここまではこの機能を使うための前座でしかないです。 draw.io for Notion – Chrome Web Store この拡張機能をいれるとNotion上で直接SVGファイルを編集できます。 まとめ 全ファイルSVGにしておくれ!
アバター
障害報告を自動生成するbot はじめに こんにちは!SREチームの島です。普段はシステム障害によるお客様影響を減らすためにチームを横断したSRE活動をしています。 今回はSlackの会話履歴から障害報告を自動生成してくれるbotを作ったのでご紹介します。他社でも似たような事例はよく見かけますが、事例が多いということは同じような課題を抱えている人が多いということだと思うので、参考になればと思い、私の体験を書きます。 こんな人に読んでほしい この記事は以下のような課題を抱えているエンジニアやSREの方に特に読んでほしいです。 障害報告を書くのに時間がかかる。情報を集めてくるのが大変 障害対応中に途中から入ってきた人に状況を説明するのが大変 障害報告を書く負担を減らしたい 作った背景 課題を把握する 障害対応って大変ですよね。私はできるだけ障害対応者の負担を減らしたいと考えていました。そこでまずは障害対応における課題を把握するためにエンジニア全員へサーベイを実施しました。 サーベイの結果 サーベイの結果 サーベイの結果 その結果、「 障害報を書くのが大変・負荷が大きい」 という意見が半数以上の人から挙がりました。他にもさまざまな課題や要望が挙がったのですが、「この課題になら自分でもすぐにアプローチできるのでは」と思い、今回の開発に至りました。 ニフティの障害対応フロー ニフティでは障害が発生するとその障害の専用チャンネルを作り、チャンネル内でやりとりを行うルールとなっています。 ニフティの障害対応フローを簡単にまとめると以下のようになります。 ニフティの障害対応フロー(概略) ニフティの障害対応フロー(概略) ニフティの障害対応フロー(概略) このフローとサーベイ結果を踏まえると以下のような課題が見えてきました。 担当者は障害対応をしながら障害報を更新しなければならない 途中から障害対応に参加した人が一目で状況を理解しづらい(人に聞くか、チャンネルを読み返すか、更新中の障害報を読むしかない) 担当者は後日、Slackの会話を見返しながら障害報を完成させないといけない この課題にアプローチするため、障害情報を要約するbotを作成し、障害報作成の負担軽減を試みました。 障害要約botの活用イメージ 障害要約botの活用イメージ 障害要約botの活用イメージ 以上の背景を踏まえて、今回作成したbotについてご紹介します。 作ったもの 障害要約bot 今回作ったbotを「障害要約bot」と名付けました。 彼はSlackチャンネル内の会話履歴を読み取って障害情報を要約してくれます。 彼は以前からニフティに存在する「myfriendGPT」という有能な友達(社内ツール)のソースをベースに作りました。 1 ベースとなった社内ツール「myfriendGPT」 つまり彼はmyfriendGPTの弟分です。そういった意味も込めてプロフィールの顔写真をmyfriendGPTと同じWEBサイト(ThisPersonDoesNotExist)で作成しました。 https://this-person-does-not-exist.com/en 実際に使ってみる 作った障害要約botを実際に使ってみました。 使い方は要約をしたいSlackチャンネルで障害要約botへ空メンションを送るだけです。 障害要約botの使い方。空メンションを送るだけで呼び出せる 使い方はなるべくシンプルにして誰でも使えるようにしました。障害対応時にしか使わないため複雑な操作が必要だと使い方を忘れてしまったり、面倒で使わなくなってしまうと考えたからです。 数秒待つと障害情報の要約を返してくれます。 返ってきた障害情報の要約 伏せ字が多く見づらくてすみません。 Slackの会話履歴から読み取れる情報を要約してくれます。Slackから読み取れない情報(この例では影響金額と影響ユーザー数)は「不明」と返されます。返す項目は障害報のフォーマットに合わせているので、これをそのまま障害報にコピーすることができます。もちろんAIなので情報が正しいか精査は必要ですが。 対応ログやエスカレーションフローも返してくれます。(上のキャプチャの続きです) 返ってきた対応ログ、エスカレーションフロー 人力で対応ログを書くとなるとSlackを遡って何時何分に何が起きたかを調べないといけないので大変ですよね。対応ログの作成には特に障害要約botの活躍を期待しています。エスカレーションフローもmermaid形式で返してくれるので、例えばエンジニア以外の方などプログラミングの知識があまりない方でもエスカレーションフローを書きやすくなるのが嬉しいポイントです。 この要約を見ることで途中から参加した人でも素早く状況を理解できますし、Scribe(記録係)の人は要約を参考にすることで障害報を書く時間を削減できると期待しています。 myfriendGPTからの変更点 上の章で、既存ツールであるmyfriendGPTをベースに障害要約botを作成したと書きました。ここではベースとしたツールから「どこ」を「なぜ」変更したのか説明します。 1. チャンネル全体の会話を読み込むようにした myfriendGPTは1対1の対話式で使うbotなこともあり、スレッド内の会話のみ読み取る仕様になっています。今回の障害要約botは複数人の会話を要約するという役目があったので、チャンネル全体を読み込むようにしました。 しかし読み込むデータ量が増えるとその分APIのRate Limitエラーなどのエラーが起きる確率も上がるという問題もありました。そこでエラーの発生を減らし、要約の精度を上げるために以下のポイントを工夫しました。 大量の添付ファイルを読み込むとエラーが発生するので、メッセージに添付された画像やファイルは読み込まないようにした 読み込むメッセージ数が多いとエラーになってしまうので、過去の障害対応期間やメッセージ数を参考に読み込むメッセージを直近7日間、最大1000件までに制限した 人間以外の発言を読み取ると要約に余計な情報が入ってしまうため、人間の発言のみ読み取るようにした。下のキャプチャがその例です。 アプリからの通知や人間の発言ではないメッセージ。これらを無視するように設定 これらを無視しないと人間以外の発言が要約に含まれてしまい精度を欠く 2. 投稿日時、 投稿者を取得するようにした メッセージの投稿日時と投稿者名をAIに渡すようにしました。そうすることでいつ、誰がという情報を読んでくれるので、より詳細な要約を作成してくれます。 When,Whoを渡すことでより詳細な要約を作成してくれる なぜAI開発未経験で作成できたのか 1からこのようなbotを作るのは結構大変だと思います。自分もAIの開発経験がなかったので何もない状態から開発するのはおそらく無理でした。 では今回なぜ障害要約botを作るのことができたのか。その要因をご紹介します。 ベースとなるソースコードがインナーソース化されていた ベースにできるソースコードが社内に存在し、そのコードが「利用できる状態になっていた」ことが要因として挙げられます。ベースにしたmyfriendGPTはインナーソース化 2 、つまり社内で使えるオープンソースとなっていました。そのため、 初見でも理解しやすいようにREADMEが充実していた 2次利用することが歓迎された という後押しもあり、スムーズに利用できました。 また、ちょうどタイミングよく「コントリビュートお試し会」というお試しでインナーソースのコントリビュート 3 をしてみようという社内勉強会があり、それに参加していたことでこのソースが使えそうだと思いつきました。まさにインナーソースの利点を活かすことができました。 日々情報収集していた これは好きで続けていたことなんですが、スキマ時間に他社の勉強会の資料をチェックしたり、SNSを見て気になった技術記事を日々チェックしていました。今回も過去に似たような事例を見た記憶があったので、真似してみようと思い作ることができました。日頃から情報収集をして脳に情報を溜めておくことは大事だと感じました。 今回のbotは以下の他社様の記事を参考にさせていただきました。 Amazon Bedrock を用いた障害対応報告書とポストモーテム文書自動作成 『全員インシデントコマンダー』の体制構築から 1年経った現在地 まとめ 今回はSlackの会話履歴から障害報告を自動生成するbotを作成しました。 まだ公開したばかりで利用実績が少なく実績を報告できないのが残念ですが、これから社内で障害要約botが活躍してくれることを願っています。(SREとしては活躍する機会が少ないのが理想ですが) 障害報作成の負担を軽減することで担当者が障害対応に集中でき、対応にかかる時間やコストを削減できることを今後期待しています。 ※ 脚注 myfriendGPTについてはこちらの記事を参照ください。こちらも一読いただけるとより理解を深められます。 ChatGPT APIを活用したSlackbotを作成して、30分以内に会社内に有能な友達を作る ︎ インナーソースについてはこちらの記事で詳しく紹介しています。 インナーソースを導入してみた その① お試し導入編 ︎ コントリビュート:自分がオーナーでないリポジトリのソースを修正してあげること ︎
アバター
エンジニアリングマネージャーをしています、芦川です。 2025-02-26 に D-Plus Tokyo #11~受けてよかった!自分が受けたい!オンボーディング Part2 にて、社内で実施している業務ドメイン知識のオンボーディングの話をしてきましたので、ブログも書きたいと思います。( 発表スライドはこちらです。 ) 20年も同じ会社にいれば、そこで初めて実感することがいくつかあります。今回もその中の1つで、プロダクトの寿命の長さや、プロダクトに関わるエンジニアの期間などの観点も含めて、オンボーディングの大事さについて触れたいと思います。 結論 この図が最も伝えたいことなので、先に示しておきます。 プロダクトを開発し、お客様に長くご利用いただくために、より便利な機能や体験を提供していきます。 より便利な機能や体験というものは、メールを送ったり、物流を通してモノを送ったり、決済手段を増やしたりと、プロダクトの周辺にものが増えてきます。これは、要するに、業務ドメイン知識です。 一方で、一般的にエンジニアはプロダクト全体の寿命に比べると携わる期間は短いものです。なので、これからもそのプロダクトを成長させるためには、開発に早く携われるようにしないといけません。 プロダクトを成長させるための開発は、単なるコーディング・テスト・デプロイという単純なものではなく、現在のお客様の体験を理解し、それを支えている仕組みを知らないといけません。つまり、周囲のものも含めて業務ドメイン知識の吸収が必要です。 そこで、業務ドメイン知識のオンボーディングが必要になり、うまくこの循環が回れば、よりプロダクトを成長させるサイクルを早くできると考えることができます。 で、業務ドメイン知識のコンテンツの作り方については記事後半にありますので、もしよろしければ、ご参考ください。 さてここからは、スライドからの抜粋と補足説明をしていきたいと思います。 ニフティの歴史は長く、10年以上続いているプロダクトも多数あります ISP事業は、すでに社会インフラとなっているのも関係しそうですが、長らくお客様にお使いいただける傾向があります。 そして、ISP的な仕組みの業務ドメイン知識とは別に、プロダクトが成長していくと、その周辺にいろいろなシステムが増えていきます。上の図はイメージですが、他にもSMSを送ったり、決済手段が増えたりといろいろあります。 そもそもオンボーディングで目指したいレベルは? 徐々にオンボーディングの話に移りますが、まず目指したいレベル感を明確にしてみると、単なる「開発できるようになる」ではなく、「お客様のために開発できるようになる」をどうせなら目指したいと思います。お客様のために、というからには、お客様が現在どんな体験をしていて、それをささえる周辺のシステムはどのようになっているかわかっている状態が望ましいです。 プロダクトの寿命と1人のエンジニアが携わる期間について で、次は時間軸で考えてみたいと思います。ある1つのプロダクトの寿命というものを時間軸として切り取り、そこに会社やエンジニアのそれぞれの時間軸をタイムラインとしてプロットしてみます。 ある1つのプロダクトの寿命に比べ、1人のエンジニアが関わる時間は一般的に短く、イメージとしてはこんな感じかと思います。キャリアチェンジや入退社など様々な理由で、結果としてこのようになります。 なので、今いるエンジニアは将来入ってくるエンジニアのことを思い、ソースコード、ドキュメントを作ることが大事であり、読みやすいコード、シンプルな設計、意図がわかるドキュメントなど、いろいろ残しておかないといけないことになります。何をどう残すかみたいな話はいろいろ話したいことがあるのですが、今日の主題はここではなく、新たにジョインする人の話にフォーカスすると、そういうものをいち早く吸収して、プロダクトを成長させるための知識を得ないといけなく、その早さが大事になるということがわかります。 目指したい好循環 つまり、これまで説明したことをあわせると、こういう循環ができると思います。 プロダクトが成長すると、周辺にものが増えてきます、これは業務ドメイン知識と同義です。エンジニアはプロダクト時間軸に対してかなり早く入れ替わりが発生するため、よりプロダクトを成長させる開発ができるようには、早く育てる必要があります。で、オンボーディングで目指したいのは、ただ単に開発とテストとデプロイができるようになることだけではなく、プロダクトをグロースさせるために必要な周辺知識を知ってほしいと思います。つまり、どんなお客様が使っているのか?お客様体験をささえている周辺の仕組みはどのようになっているのか、ということです。それらを知っていることで、プロダクトの成長を見据えた開発、もっとアイデアが出たり何かを効率化したり、ということができてくるのかな、と考えています。 以上が、ドメイン知識のオンボーディングをすることにより、エンジニアの成長を早め、継続的にプロダクトの成長を促す好循環をもたらしていく、という説明でした。 どうやってドメイン知識のオンボーディングコンテンツを作ったか? ここからはオンボーディングコンテンツ作成の話です。上のスライドにあるような順番で作成していきました。社内にいるドメイン知識のマスターたち(一番知ってそうな方)にお声がけしていき、それぞれのドメインでの担当者を決めて約1時間くらいの講義 / 小テスト 形式で、コンテンツを作成していただきました。さらにすべての講義については録画をすべて取り、今後ジョインする方々がすぐに閲覧できるようにしました。コンテンツについては、たいてい社内に既存のドキュメントがあるため、そこへのリンク集のような形で話の流れをまとめてもらう1ページのような想定です。 とてもじゃないですが、細かいところまで知ろうとすると非常に時間がかかることになり難しくなると思うので、浅く広く知ってもらうことを重視する考え方です。濃さの調整ですが、あるドメイン知識の講義時間を1時間確保するので、その中で伝えられる程度にしてください、と先に時間制限を設けました。 コンテンツはでき次第、講義を開催していき、既存メンバーに対して教育していきます。 結果的に、各ドメイン知識ごとに、ドキュメントの URL やビデオ集のページが作成されます! すばらしい、これは宝です。 あとは、新しくジョインしていただいた方に漏れなくこのコンテンツが伝わるよう、Slackワークフローを使って自動的にメッセージが飛ぶように仕掛けました。 最後に 長期的なプロダクトの成長を支えるためには、技術力と業務知識の両方が不可欠です。特に、プロダクトの寿命が長く、複雑な業務知識が必要な場合、この両輪のバランスが重要になってきます。 私の経験から、効果的なオンボーディングとは、単なる技術スキルの学習ではなく、プロダクトを取り巻く業務全体への理解を深める機会なのだと思います。プロダクトを成長させるエンジニアは、コードを書くスキルと同じくらい、ビジネスや業務への深い理解が求められていると思います。 というところで、以上、プロダクトの成長を継続的に促すには「技術スタックだけじゃない、業務ドメイン知識のオンボーディングも同じくらいの量が必要な話」でした。
アバター
ニフティにはベテランから若手まで、多種多様なエンジニアが働いています。その考え方やキャリアパスも人それぞれですが、基本的には社員自身が主体的にキャリアについて考え、自ら道を切り開いていく自由な風土があります。今回は「マイ ニフティ」のサービスを手掛ける開発メンバー3名にインタビュー。エンジニアから見たニフティという会社の環境について、キャリアパスについてなど、現場で働く開発者ならではのリアルな視点で話を聞きました。 自己紹介 K.Nさん 2009年入社。デザインチームにコーダーとして配属され、ウェブサイトのディレクションを担当。2015年から、社内のスクラムエバンジェリストとして活躍中。CSMやA-CSMの資格を持ち、10チーム以上、40名以上のエンジニアに対してスクラム導入やサポートを実施してきた。スクラムマスターギルドを主催し、社内のアジャイル文化の醸成に貢献。プライベートでは、アフタヌーンティーとボードゲームを愛する一面も。社内ボードゲーム部の部長として、ドミニオンの普及にも尽力している。 M.Kさん 2019年新卒で入社。2022年から会員向けiOS/Androidアプリ「マイ ニフティ」の開発・運用を担当。チームに入った当初はiOS/Andoirdアプリ開発は未経験だったが、現在はアプリ自体はもちろんバックエンド、コンテンツ管理用の内製ツールまで幅広く担当している。最近は某パフォーマンスチューニングコンテストの振り返りをしながら、担当システムに活かせることがないか模索中。 A.Mさん 2019年新卒で入社。「ニフティ ニュース」チームに約4年半所属し、その中で、2021年のマイニフティアプリの立ち上げ時にiOSアプリを主導して開発。体制変更に伴って、2024年3月にマイニフティチームに異動。Jリーグ「横浜F・マリノス」のサポーターでもあり、サッカー駆動で全国各地に遠征をしている。家電などに詳しい。 お互いを高め合える同期エンジニアの存在 みなさんは「マイ ニフティ」を手掛けるチームのメンバーということですが、お互いに対する印象を教えてください。 K.Nさん 私は二人よりも10年先輩で、「マイ ニフティ」のスクラムマスターをやっています。A.Mとは前部署で「ニフティニュース」というサービスのアプリ開発を一緒にやっていた仲間でもあり、戦友のような存在ですね。アプリのエンジニアとして、非常に頼もしく感じています。ちなみに、彼はサッカーやポイ活、家電製品に詳しくて、私が家電を買い替える時は必ず相談して、薦められるまま買っています。 M.Kとは、彼が2022年に「マイ ニフティ」にジョインした時からの付き合いです。彼の長所は指摘されたことを真剣に受け止める素直さ。それまでアプリ開発の経験はなかったのですが、すぐに主戦力になって、今ではチームを牽引してくれています。漫画の趣味が合うので、よく好きな作品について話しますね。 私から見て、彼らは理想的な関係性だと思います。お互いにフォローし合いつつ高め合って、すごく良いムーブが生まれている。これからもタッグでチームを牽引してほしいですね。 では、K.Nさんはお二人にとってどんな先輩、リーダーでしょうか? A.Mさん 僕が1年目の頃からお世話になっていますが、「温室」で育ててもらった感覚があります。もともと初対面の人とのコミュニケーションが苦手だったのですが、たとえば業務でチーム外の人と調整を行う時などは、K.Nさんにかなりフォローしていただきました。また、どちらかというとネガティブに考えてしまう性格も、ポジティブなK.Nさんに影響されて少しずつ変わってきましたね。今は落ち込むことがあっても持ち直す力がだいぶ身についたと思います。 M.Kさん 僕は新人の頃から社内インフラの開発や運用に携わっていたこともあり、「マイ ニフティ」のチームに異動した当初、ユーザーさん向けのサービスに関する知識がほとんどありませんでした。ただ、K.Nさんと企画チームのやりとりを見て、ユーザーさんに使ってもらうためにはどういう視点が必要か、いかにサービスを運用すべきかといったところを学びましたし、必要なスキルを身につけるためにサポートしていただきました。 「マイニフティ」は会社の顔。下手なものは出せない みなさんが手掛ける「マイ ニフティ」とは、どのようなサービスなのでしょうか? K.Nさん @niftyの会員様向けアプリで、2021年にiOS版、2022年にAndroid版をリリースしました。@niftyのトップページやサポートページ、各サービスサイトなどが集約されていて、アプリを開けば知りたい情報にアクセスできます。また、利用料金やNifMoの通信料を確認したり、@niftyメールをチェックしたり、お知らせなども含めてホームタブで一気見できるようになっています。一言で言えば、お客様がインターネットやニフティのサービスを快適にご利用いただけるよう、サポートするためのアプリですね。 K.NさんとA.Mさんは「マイ ニフティ」の立ち上げメンバーで、M.Kさんは2022年のAndroid版の開発メンバーとしてアサインされたと伺いました。M.Kさんはそれまでアプリ開発の経験がなかったということで、かなり苦労されたのでは? M.Kさん 正直、大変でした(笑)。ただ、そもそも「マイ ニフティ」には自らの希望で異動してきたので、未経験ながらアプリ開発を任せてもらえるのはありがたかったですね。もちろん、それまでの社内システムの業務にもやりがいを感じていましたが、徐々に顧客向けのサービスにも携わってみたいと考えていた頃に、社内で「公募制度」というものがスタートしたんです。新しい人材を必要としているチームが全社に向けて募集をかけられるというもので、「マイ ニフティ」でも開発メンバーを募っていました。ちょうどAndroid版の開発が始まったところで、市場でもニーズの高いアプリ開発に携われるチャンスだと考え、トライしてみようと。 ただ、大変だったと。 M.Kさん 当初は本当に何も分からず、どこから始めればいいんだろうという状況で。ただ、先輩が丁寧にサポートしてくれましたし、自分でもアプリ開発者が集まるカンファレンスなどに参加して、そこで聴いたセッションの内容を実務で試してみたりと、色んな角度から知識を深めていきました。 また、僕が入ったタイミングではすでにエンジニア同士の議論が尽くされていて、基本的な設計や構造などはあらかじめまとめられていました。そのため、自分はその流れに乗りつつアプリの学習やスキルの習得に注力できたところもありますね。 A.Mさんはいかがでしょう? 立ち上げ時に苦労したこと、課題に感じていたことはありますか? A.Mさん それまで「ニフティ ニュース」のアプリに携わっていたとはいえ、運用や新しい機能の開発がメインで、さすがにイチからアプリを立ち上げた経験はありませんでした。どういう技術を使えばいいのかといった、本当に入口の部分から調べてやっていく大変さはありましたね。また、単純にタスク量が多く、期日までに終わるのかという不安や焦りもありました。 ただ、新規サービスの立ち上げはなかなか経験できることではないですし、開発を通じて成長できた実感があります。自分がこれまで触れてこなかった社内のドメイン知識も全て活用しながら開発にあたったことで、アプリのことだけでなくニフティ内部の情報の回り方みたいなところも含めて理解が深まりましたね。 K.Nさんはチームリーダーとして、サービス全体のクオリティなども見なければいけない立場だったと思います。当時、どんな意識で開発に臨んでいましたか? K.Nさん お客様からすると、「マイ ニフティ」はニフティの顔だと思います。そんなサービスが何度もクラッシュしたり、うまく使えなかったりすると、ニフティに対する印象も悪くなってしまう。そんな意識で開発にあたっていました。また、今後もずっと運用していくサービスになると考えていましたので、最初にどこまでつくるのか、どれくらい先のことまで考慮してつくるのか、チームで何度も議論を重ねましたね。 それらの苦労を経てiOS版をリリースした直後のお客さんの反応はいかがでしたか? K.Nさん 企画チームが周知を頑張ってくれたおかげで、早い段階で10万DLに到達しました。使っていただいたお客様からも、概ね高い評価をいただけて。ただ、リリース当初から目標として掲げていたのは、単に便利というだけでなく、@niftyのサービスを長く使っていただくうえで「なくてはならないアプリ」にまで持っていくことでした。極端に言えば、「マイ ニフティ」があるから@niftyを契約しましたと言ってもらえるくらいの機能と魅力を持つサービスに成長させていかなければならないと考えています。 後編に続きます! 今回はニフティのマイ ニフティチームのインタビューの様子をお届けしました。続きは近日公開予定の後編の記事をご覧ください。 このインタビューに関する求人情報 /ブログ記事 ニフティ株式会社 求人情報
アバター