TECH PLAY

ニフティ株式会社

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

487

はじめに こんにちは、ニフティの添野 隼矢です。 今回は、驚くべき出来事をご紹介します。なんと、クリエイティブ職の方※が直接コンテナ環境でデザインを修正し、GitHubでPRをあげてくださったのです!この画期的な出来事の舞台裏に迫ります。 ※クリエイティブ職の詳細については、こちらをご覧ください。 https://recruit.nifty.co.jp/career_top/ 経緯:施策スピードアップへのチャレンジ とあるサービスの施策で、画面にバナーを追加する作業がありました。 従来の課題 通常のフローでは、以下のような流れになります: 1. クリエイティブ職の方がデザイン要件を整理 2. 開発担当とのデザイン調整(複数回のやりとり) 3. 実装・レビュー・デプロイ このデザインやりとりで時間がかかってしまい、施策開始が遅れてしまうことが課題でした。 今回の経緯 そこでクリエイティブ職の方から「デザイン修正を自分たちでやってみる」という提案をいただきました。 既存の開発環境が生んだ偶然 もともと整備していた環境 私たちは自身の開発効率向上のために、以下のローカル開発環境を構築していました。 フロントエンド SvelteKit 開発環境 DevContainerで統一 品質管理 linter/formatter自動適用 コンテナ管理 Rancher Desktopを採用 これらの環境が、予想外にも「クリエイティブ職の方でも触りやすい」環境になっていました。 SvelteKit: エンジニア向けに選択したSvelteKitが、結果的にHTMLライクでクリエイティブ職の方にも理解しやすい構造でした。 DevContainer: 「エンジニア間で環境を統一したい」という目的で導入したDevContainerが、「誰でもワンクリックで開発環境を起動できる」仕組みとして機能しました。 Rancher Desktop: 無料で誰でもつかえるツールとして、クリエイティブ職の方にも導入しやすい環境を提供してくれました。 自動linter/formatter: エンジニアのコード品質を保つために導入していた自動フォーマッターが、クリエイティブ職の方が書いたコードも自動的にきれいに整形してくれました。 実際の作業プロセス 前提条件 バナー画像:クリエイティブ職の方で作成済み HTML/CSS:知識があり、触っている 実作業の流れ 環境構築サポート Rancher Desktopのインストールと初期設定のみサポート DevContainer起動 VSCodeのボタンひとつでローカル開発環境が完成 コード修正 普段のHTMLを書く感覚でSvelteファイルを編集 リアルタイム確認 ローカル環境ですぐに変更を確認 自動品質管理 保存時に自動でコードがフォーマット PR作成 クリエイティブ職の方からGitHubのPRを作成 成功要因の分析 実際にこの取り組みが成功したのは、前述の仕組み的な部分ももちろんですが、弊社に根付いている以下の文化も良い方向に作用しているように思います。 クリエイティブ職の方の積極性 組織内の垣根を超えて、自由に挑戦できる環境が弊社ではあります。 失敗を恐れない文化 弊社では、問題が発生した際に個人を責めるのではなく、仕組みやシステムの改善にフォーカスする文化が根付いています。 適用範囲と制約事項 今回成功した要因として、以下の前提条件があったことも重要です。 適用可能な作業 HTML/CSSの知識がある方による軽微なデザイン修正 画像やテキストの差し替え レイアウトの微調整 従来通りエンジニアが必要な作業 ロジックを伴う機能追加 データベース関連の修正 セキュリティに関わる変更 また今回、あげてくださったPRをエンジニア側でレビューしています。 おわりに 今回の経験から得られた知見は以下です。 開発環境の民主化が組織全体の生産性向上につながる 技術選択の副次的効果を意識した環境設計の重要性 組織文化と技術環境の相乗効果 この気付きをもとに、みなさまの環境の参考にしていただければ幸いです。
アバター
はじめに こんにちは。会員基盤チームの添野 隼矢です。 私はこれまで、課金請求系、セキュリティ、会員管理・ログイン認証など、様々な分野でキャリアを積んできました。現在は会員管理システムの開発に携わっています。 今回は、私が所属している会員基盤チームが持っているプロダクトと私たちのチームが挑戦する壮大なプロジェクトについてご紹介します。 会員基盤チームのプロダクト 当チームが管理している主要なプロダクトは以下の3つです: 会員管理システム クレジットカード情報を中継するシステム 認証・認可を行うシステム 大規模刷新プロジェクトの概要 現在、私たちのチームは下記の募集要項にあります通り、500万人を超える会員を管理するシステムの大規模刷新プロジェクトに取り組んでいます。 このプロジェクトは、単なる移行ではなく、未来のビジネスを支える基盤づくりを目指しています。 https://hrmos.co/pages/nifty/jobs/10303 まずは、現在のシステムをスリム化、コンテナ化しながらAWSに移行しています。 AWSに移行する際には、スケーラビリティと耐障害性を考慮した設計をした上で、Terraformを用いて、インフラ環境のリソース作成を進めています。 また100本以上あるAPIやバッチ処理をコンテナ化することで、現状デプロイしないと動作確認ができなかった処理をローカルで確認できるように改善しています。 AWSへの移行後は、お客様への利便性向上と新規ビジネスの早期立ち上げを目的とし、ISP固有の会員基盤からの脱却に向けて、各機能のマイクロサービス化を進めていきます。 まずは、目的を実現するために何が必要かなどの要件定義を、企画や営業の方、お客様サポートを担当している方を含めて行っております。 また、今後Javaで作られているシステムを、弊社内で主流となっているPythonへの切り替えを予定しています。 おわりに 現在、会員基盤チームではキャリア採用を募集しています。 500万人の未来を支える技術を、一緒に創り上げませんか? 経験豊富なエンジニアの方、ぜひご応募ください。 https://hrmos.co/pages/nifty/jobs/10303 また所属しているチームのインタビュー記事が本ブログにて公開されています。 https://engineering.nifty.co.jp/interview/33486 そちらでは、会員基盤刷新プロジェクトの経緯や会社全体のことなどを話していますので、ご興味があればお読みください。
アバター
はじめに こんにちは。ニフティ株式会社の山田です。 今回は、フロントエンドフレームワークとして最近SolidJSを試してみたので、その内容を紹介いたします。 そもそもSolidJSとは? SolidJSとは、仮想DOMを使用しないJSX系宣言的UIフレームワークです。 宣言的UIフレームワークとしてはReactやVue.jsが有名ですが、この2つは差分レンダリングに仮想DOMを利用しています。 これは仮想DOMに一度書き込んだあと、差分を計算して実際のDOMに反映させるという動作を基本としています。プログラミングモデルが単純化される一方で、仕組みとしては重く、JavaScriptのサイズを肥大化させる原因ともなっています。仮想DOMへの書き込みを減らす仕組みも入っていますが、根本的な重さからは逃れられません。 一方で最近登場したのが、イベント駆動とトランスパイルを組み合わせるフレームワークです。 これらのフレームワークは、変数を「値変化イベントを発火させるオブジェクト」として取り扱うことで、その変数に関連するコンポーネントだけを再レンダリングするように動作します。 普通であればコーディングが複雑化しますが、ビルド時変換を多用することで極力簡単に書けるようになっています。 有名なフレームワークとしてSvelteが挙げられますが、SvelteはVue.jsに近いHTML/CSS/JavaScriptを1ファイルに書く独自文法を採用しています。 一方で、React同様にJSXを採用しているのがSolidJSです。 SolidJSは値変化の検出にsignalという仕組みを採用しています。この仕組みはknockout.jsで開発されたものですが、宣言的UIフレームワークに採用したのはSolidJSが初であり、その後PreactやVue.js、Svelte 5などでも採用が広がっています。 SolidJSのメリット 独自文法を採用せず、JSXベースのためセットアップが容易 SvelteはLinterなどのセットアップが辛い… Astroに部分的に組み込む〜 などがやりやすい 読みにくい挙動部分が少ない この辺りもReactの思想に近い Svelteのように Easy >>> Simple な思想とは真逆 仮想DOMを使用せず、動作速度・JSサイズともに軽量 デメリット 利用者がまだ少ない… React > Vue.js > Svelte >>>>> SolidJS くらいの肌感覚 周辺ツールのサポートも少ない Vitest Browser Mode 非対応なのが痛い SSRフレームワーク(SolidStart)の歴史が浅い 2024年4月にようやく1.0になったばかり 使い心地 基本的なコンポーネント import { createEffect, createSignal, onCleanup } from "solid-js"; const CountingComponent = () => { const [count, setCount] = createSignal(0); const interval = setInterval( () => setCount(c => c + 1), 1000 ); onCleanup(() => clearInterval(interval)); createEffect(() => { console.log(`count: ${count()}`); }); return <div>Count value is {count()}</div>; }; Reactに慣れている人であれば、 createSignal → useState createEffect → useEffect のように読み替えて理解ができるくらいにはそっくりです。 Reactでは状態変数をstateと呼びますが、Solidではsignalと呼びます。値の変化に応じて値変化イベントを発火させるオブジェクトになっています。 Reactとの違いとしては、以下となります。 コンポーネントは初期化時の1回しか実行されないこと Reactのように再レンダリングの度に実行される、というモデルではない signalが変化した場合、signalに関連する部分のコードだけを再実行する 定数宣言してしまうと初期化時に値が固定されてしまう signalに依存する別の変数を扱う場合は、関数として扱います const [base, setBase] = createSignal<number>(1); // Bad: これは初期化時に値固定され、以降変わらなくなる const multiply = base * base; // Good: これはbaseの変化に応じて変化する const multiply = () => base * base; returnは必ず1つでなければならない 複数あったとしても、使われるのは初期化時に使われた1つだけ 変化する値は関数を通してアクセスする 例えばcreateSignal()で返るのは定数ではなく、関数になっている TypeScript的にはAccessor<T>という型になっている type Signal<T> = [get: Accessor<T>, set: Setter<T>]; type Accessor<T> = () => T; type Setter<T> = (v: T | ((prev?: T) => T)) => T; createEffectに依存配列は必要ない ReactのuseEffectとは明確に異なる createEfffectの中で使われているsignalが変化したとき、自動で再実行される props import { type JSX } from 'solid-js'; type Props = { titile: string; url: string; }; const Component = (props: Props): JSX.Element => { return <a href={props.url}>{props.title}</a> }; propsもReact同様…ですが、オブジェクトの展開(destructuring)を行ってはいけないです。 (以下のように書くのはアウトとなります) const Component = ({title, url}: Props): JSX.Element => { ... }; // これはは以下の構文と等しい // つまり初回実行時の値をconstで取り出して固定化しているので、値の変化に追従しなくなる // const Component = (props: Props): JSX.Element => { // const { title, props } = props; // ... // }; 制御構文 「変数(signal)の変化に応じて再レンダリングする」という構造上、コンポーネントの出し分けに三項演算子やmap()を使用してしまうと無駄な再レンダリングを引き起こしてしまう可能性が高いです。(条件文の結果が変わらなくても、条件判定に使用するsignalが変わっていれば再レンダリングされるため) というわけで、コンポーネントの出し分けには独自のコンポーネントが必要になります。 Show <Show when={/* 条件文 */} fallback={/* else相当のコンポーネント */} > {/* コンポーネント */} </Show> if文や三項演算子の代わりになるのがShow。whenに条件文を渡して制御します。else相当のコンポーネントはfallbackに渡します。 残念な仕様として、whenによる type narrowingが効かない です。このためasなどを使用せざるを得ません。 const data: Accessor<string | string[]>; <Show when={!Array.isArray(data)} > {/* whenと↓のコンポーネントは文法上の紐づけがないので、!isArray()による型の絞り込みがされない */} <SomeComponent data={data() as string}> </Show> 一応、子コンポーネントを「whenの値を渡す形の関数」として記述でき、null・undefined判定だけならこれで解決できます。 const data: Accessor<string | undefined>; <Show when={data} > {(data) => <SomeComponent data={data() /* string型 */}> } </Show> For / Index forやmap()の代わりはForとなります。 <For each={/* forで回せるデータ */} > {(item) => <SomeComponent data={item()}>} </For> 同じ文法でIndexというものもあります。それぞれの使い分けはドキュメントに記載されていますのでそちらを参照してみてください。 https://docs.solidjs.com/reference/components/index-component createResource データの非同期取得のために、createResouceが標準で用意されています。 ReactのSWRやTanstack Queryのような別ライブラリを必要とせずにデータフェッチを記載できます。 const fetchUser = async (userId) => (await fetch('https://....')).json(); const userId = createSignal<string>(""); // 第一引数に与えたsignalに応じて自動的に第二引数の非同期関数を実行する const [user] = createResource(userId, fetchUser); // refetchなどを行いたい場合は追加の戻り値もある const [user, { mutate, fetch }] = createResource(userId, fetchUser); まとめ Good Point 挙動が理解しやすい Svelteのような、どう動くのか理解不能な局面が少ない 軽い Reactで書くと数百KBになるものが数KBで済む 運用が軽い 破壊的変更が非常に少ない Svelteは周辺ツールもふくめてそれなりに壊してくる & Svelte 5で超大規模変更が… JSXなので大体のツールが標準対応しており、余計なパーサーなどの設定が不要 罠はあるが、入門ドキュメントで覚えた程度で事足りる 公式ドキュメントに日本語あり Bad Point Showが辛い ここだけ型安全性が崩れる SolidStartの歴史が流石に浅い まだ半年程度… ベースになっているvinxiがまだ0.4でドキュメントも貧弱 Astro + Solidのような組み合わせが現状は安牌 今回はSolidJSを試してみた所感を紹介しました。SolidJSや他フレームワークの比較をされている際に参考になれば幸いです。
アバター
ISP(インターネット接続サービス事業者)を主力事業に、成長を続けてきたニフティ。その屋台骨を支えてきたのが、創業当初からの会員基盤システムです。まさに会社の財産といえる一方で、老朽化したシステムによって新規サービスの立ち上げ速度が鈍るなど、長く同じ基幹システムを使い続ける弊害も生じていました。 2024年4月に立ち上がった「会員基盤刷新プロジェクト」では、この基幹システムを大きく見直し、2027年度中に抜本的なリニューアルを行うことを目指しています。ニフティのビジネスやサービスを、根本から変える可能性を秘めた一大プロジェクト。重積を担うメンバーに話を聞きました。 自己紹介 R.Aさん 2004年、新卒入社。バックエンドエンジニアとして、お客様向けログインシステムに関する開発・運用などを担当したのち、2018年からエンジニアリングマネージャー。現在は、会員基盤刷新プロジェクトのリーダーも務める。趣味は筋トレとジョギング。 S.Nさん 2007年、新卒入社。以降、一貫してバックエンドの会員基盤、認証基盤などの開発・運用業務に従事。社内のさまざまなプロジェクトに、バックエンドエンジニアとして携わる。会員基盤刷新プロジェクトではサブリーダーを担いつつ、開発、運用、他部署との調整業務などを幅広く担当。趣味は楽器演奏。トリニダード・ドバゴ共和国発の打楽器であるスティールパンのほか、オーケストラでトランペットも演奏する。 S.Sさん 2019年、新卒入社。課金キャンペーンに関する開発・運用やセキュリティなどの運用を経て、2025年から会員基盤刷新プロジェクトに参加。兄もニフティのエンジニア。趣味は音楽鑑賞。最近は推しのライブに行くこと。 ニフティのビジネスを大きく変える「会員基盤刷新プロジェクト」とは? みなさんは2024年4月からスタートした「会員基盤刷新プロジェクト」を推進する中心メンバーということですが、はじめにプロジェクトの概要を教えてください。 R.Aさん @niftyの会員基盤システムは、いわばニフティ全社のビジネスを支える最重要のシステムです。この会員基盤全体を刷新し、お客様の利便性向上と新規ビジネスの早期立ち上げをはかることを目的としたプロジェクトになります。 S.Nさん 現在の@niftyの会員基盤は、ISP(インターネット接続サービス事業者)の創成期につくられたシステムです。これまで、何度も刷新を重ねてニフティの主力事業であるISP事業を支えてきた、会社の財産といえます。一方で、創業以来の老朽化したシステムであるがゆえの弊害も起こっていました。たとえば、新しいビジネスを立ち上げようとしても、「従来のシステム上にどう落とし込むのか」といった、無駄な議論が発生してしまっていたんです。そうした、ISP固有の会員基盤から脱却し、各機能のマイクロサービス化を進めようというのが、プロジェクトの最も大きな狙いですね。 ISP事業だけでなく、これから新たなサービスをどんどん生み出していくためにも、基幹システムの抜本的なリニューアルが必要だったと。 S.Nさん そうですね。今回のプロジェクトでは「XSP」というキーワードを掲げています。このXの部分にはさまざまなサービスがあてはまり、ISP事業もそのうちの一つです。つまり、ニフティという会社は今後、ISPだけでなく多種多様なビジネスを展開していく会社になる。それを可能にする基盤をつくるのが、私たちのミッションですね。 新規サービスの立ち上げだけでなく、お客様の利便性向上の側面もあるということですが。 S.Nさん 一例を挙げると、現状はシステムの都合上、サービスごとに複数のIDを取得しなければいけないケースもあります。会員基盤を刷新してシンプルにすることで、一つのIDでニフティの全てのサービスをご利用いただくなど、お客様にとってより使いやすいものにできるのではないかと考えています。 現状のプロジェクトの進捗と、今後の展望を教えてください。 R.Aさん 2024年4月にプロジェクトがスタートし、現在まで1年をかけて各部署のマネージャーたちと議論を重ねてきました。社内のあらゆるサービスに関わるシステムですので、関係者がめちゃくちゃ多くて。さまざまな立場の人の意見を吸い上げて調整したり、開発の優先順位を決めたりする必要があったんです。現状は、ようやく計画がフィックスしたところで、2025年の4月からいよいよ開発がスタート。2027年度までに段階的にリリースをしていく予定です。 他部署と「幸せな未来」を共有し、温度感を合わせる プロジェクトにおける、みなさんの役割を教えてください。 R.Aさん プロジェクトリーダーとして、メンバーが動きやすい体制を整えること、また、関係者とのコミュニケーションが主な役割です。特に、各部署のマネージャー陣との調整や交渉などがメインですね。 S.Nさん 私も各部署との調整を行いますが、上長レベルのコミュニケーションはR.Aさんにお任せして、私の方では現場レベルでの細かい設計などについての調整を担当しています。他には、開発運用、コーディングまで、本当に何でもやるような感じですね。 S.Sさん 私も同じく何でも屋です。設計、運用をはじめ、システムの構築にまつわる全てを担当しています。 S.Sさんは3人のなかで最も若手ですが、会社として最重要ともいえるプロジェクトを手がけることを、どのように捉えていますか? S.Sさん 今後のニフティのビジネスを支えるシステムとして、長く使われていくものになるので、もちろんプレッシャーはあります。設計ひとつとっても高い品質を求められていますし、私自身もさらに成長しなければいけないなと。 特に難しさを感じているのは、他部署からの要望やお客様のニーズなど、さまざまな要素に対応する必要があること。私の場合はチームにアサインされて日が浅いこともあり、現状のシステムにおける課題感を掴みきれていないところもありました。ただ、当初から週に1度の頻度で他部署の方々とミーティングを重ねてきて、徐々に解像度が上がってきましたし、要求をまとめる力も身についてきたと思います。 R.Aさん、S.Nさんは、自身の役割のなかで難しさを感じる部分はありますか? R.Aさん 私の場合は、周囲とのコミュニケーションですね。特に、このプロジェクトは他部署のメンバーにも実際に手を動かしてもらうことが必須で、協力を仰ぐためにはプロジェクトの意義や目的を理解してもらう必要がありました。 当初は関係者を集めてディスカッションをしようにも、なかなかうまくいかないところもありましたね。積極的に意見を出してもらえなかったり、堂々めぐりの議論になってしまったりと、関係者が多いゆえに思うように進まないストレスを感じていました。ただ、半年前ほど前からコミュニケーションや会議の進め方を見直して、徐々に議論が噛み合うようになっていったと思います。 S.Nさん 他部署にどこまでお願いしていいのか、そのバランスは難しいですね。やはり、これをやることで「どれだけ幸せな未来が待っているか」を、丁寧に伝えて温度感を合わせていく必要があると思います。 また、このプロジェクトは2027年度いっぱいかかる想定なので、「その間、他の開発ができなくなるんじゃないか」という懸念の声は多く挙がっています。私自身もプロジェクトのメンバーであると同時に、会員管理システムの開発・運用も担う立場ですので、そうした不安もよく分かる。システムの刷新と、その間の新規の開発をいかに両立していくかという点は大きな課題の一つですね。 チームは新たな仲間を募集中。望むメンバー像は? プレッシャーも大きく難易度も高いミッションですが、そのぶん、やりがいも感じられそうです。 S.Nさん 個人的にはプレッシャーよりも、このプロジェクトに携われる喜びやワクワクのほうが大きいです。@niftyにはすでに500万人を超える会員様がいらっしゃいますが、会員基盤システムを構築して新しいサービスがどんどん誕生することで、お客様もさらに増えるはず。そう考えると、とてもやりがいの大きい仕事だなと。 これから実装に向けてプロジェクトが本格化しますが、現状の課題を教えてください。 R.Aさん 一つはチーム体制の強化です。2025年4月から、もう一つチームをつくり人も補充していく予定です。社内でもメンバーを公募していますし、本プロジェクト向けのシステムエンジニアの求人も行っています。 どんなメンバーを迎えたいですか? R.Aさん まずスキルの面でいうと、設計から入る作業が多いので、コーディングだけをバリバリやってきた人よりは、上流工程の基本設計の経験をお持ちの方を求めています。性格の面では、ありきたりですが「この人と働いてみたい」と思えるような人間性を備えた方。僕だけの判断ではなくメンバーにも面談に入ってもらい、一緒に働く仲間を選んでほしいと考えています。 S.Nさん 僕は、日頃からアーキテクチャに対して「妄想」を膨らませている方に来てほしいです。こうした基幹系のシステムは、エンジニアからすると「もっと、こういうふうになっていたらいいのに」と思うことが多々あると思うんです。そこで「自分ならこうつくる」といった具合に思いをめぐらせている方がチームに入ってくれると、より議論が深まり、良いものができると思います。 性格面でいうと、積極的に動いてくれる人ですね。このプロジェクトは特に、上から言われたことをやるのではなく、自分たちでやるべきことをつくり出すという側面が強い。論理的思考に基づき、積極的にアクションを起こせる人が来てくれると、本当にありがたいです。 後編に続きます! 今回はニフティの会員基盤刷新プロジェクトチームのインタビューの様子をお届けしました。続きは後編の記事をご覧ください。 このインタビューに関する求人情報 /ブログ記事 ニフティ株式会社 求人情報
アバター
はじめに こんにちわ! マイ ニフティチームの寺島です。 普段はスマートフォン向けのアプリケーション開発に携わっています。 ブログ運営チームのメンバーでもあります! 機能開発やバグ修正を進めていると、途中で「typo修正」「ログ出力追加」「ちょっとリファクタ」など、小さなコミットが積み重なることがありますよね。そのままプルリクエストを出すと、コミット履歴が細かすぎてレビューしづらくなったり、プロジェクト全体の履歴がノイズが多くなってしまったりします。 そんなときに役立つのが、Gitの rebase -i コマンドを使った「コミットの整理」です。 特に 複数のコミットを一つにまとめる(squashする) 方法は、プルリクエストを出す前の準備として非常に有効です。 今回、実際に手を動かしながら、 git rebase -i を使って複数コミットを一つにまとめる手順を解説します。 ハンズオン用のリポジトリを作成する 今回のハンズオンで使用するダミーのリポジトリを作成します。 適当なディレクトリで以下のコマンドを順に実行してください。 # 作業用ディレクトリを作成 mkdir git-squash-demo cd git-squash-demo # Gitリポジトリを初期化 git init # ダミーファイルを作成し、最初のコミット echo "Hello, Git Squash" > README.md git add README.md git commit -m "feat: add initial README file" # 状態確認 git log --oneline git log --oneline を実行すると、最初のコミットが表示されるはずです。 次に、まとめる対象となる複数のコミットを作成していきます。 いくつかの変更を加え、それぞれ順にコミットしてください。 # 2つ目のコミットを作成 echo "This is the second line." >> README.md git add README.md git commit -m "chore: add second line" # 3つ目のコミットを作成 echo "This is the third line for refactor." >> README.md git add README.md git commit -m "refactor: improve wording" # 4つ目のコミットを作成 echo "Add final sentence." >> README.md git add README.md git commit -m "feat: add final sentence" # 現在の履歴を確認 git log --oneline git log --oneline を実行すると、以下のように4つのコミットが積み重なっていることが確認できるはずです。(ハッシュ値は環境によって異なります) <最新のハッシュ> feat: add final sentence <3つ目のハッシュ> refactor: improve wording <2つ目のハッシュ> chore: add second line <最初のハッシュ> feat: add initial README file これで準備は完了です! 最新の3つのコミット(”chore: add second line”、”refactor: improve wording”、”feat: add final sentence”)を一つにまとめてみましょう。 rebase -i を開始する git rebase -i コマンドは、「どのコミットまで」を対象にするかを指定して実行します。 指定したコミット より新しい コミットがインタラクティブなrebaseの対象となります。 今回は最新の3つのコミットを対象にしたいので、「最初のコミット( feat: add initial README file )」の次からを対象にします。 最新のコミットから数えて対象のコミットが何番目か分かっている場合は、 HEAD~N という形式で指定できます。 今回は最新の3つのコミットを対象にするので、 HEAD~3 を指定します。 これは「HEADから3つ遡ったコミットまで」を意味し、結果的に最新の3つのコミットが操作対象になります。 # 最新から3つのコミットを対象にインタラクティブrebaseを開始 git rebase -i HEAD~3 このコマンドを実行すると、エディタが立ち上がります(使用するエディタはGitの設定によります)。エディタには、対象となるコミットが古い順に表示されます。 pick <2つ目のハッシュ> chore: add second line pick <3つ目のハッシュ> refactor: improve wording pick <最新のハッシュ> feat: add final sentence # Rebase <ハッシュ値>..<別のハッシュ値> onto <別のハッシュ値> (<コミット数> ahead) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous # commit's log message, unless -C is used, in which case # keep only this commit's message; -c is same as -C but # opens the editor # x, exec <command> = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop <commit> = remove commit # l, label <label> = label current HEAD with a name # t, reset <label> = reset HEAD to a label # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] # create a merge commit using the original merge commit's # message (or the oneline, if no original merge commit was # specified); use -c <commit> to reword the commit message # u, update-ref <ref> = track a placeholder for the <ref> to be updated # to this position in the new commits. The <ref> is # updated at the end of the rebase # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # エディタでコミットを整理する エディタには、各コミットの左側にコマンドが記述されています。デフォルトは pick になっており、そのコミットを採用するという意味です。 複数のコミットを一つにまとめたい場合は、 まとめたいコミットの行の pick を squash または s に変更します。 squash はそのコミットを 直前のコミットにまとめます 。 s は squash の短縮形です。 今回は最新の3つのコミット(”chore: add second line”, “refactor: improve wording”, “feat: add final sentence”)を、一番古いコミット(”chore: add second line”)にまとめたいです。 2番目と3番目のコミットの pick を s に変更します。 エディタの内容を以下のように修正してください。 pick <2つ目のハッシュ> chore: add second line s <3つ目のハッシュ> refactor: improve wording <- ここを s に変更 s <最新のハッシュ> feat: add final sentence <- ここを s に変更 # コメント行はそのまま、または削除してOK ... ※一番上の行(今回まとめたいコミット群の中で一番古いもの)は pick のままにしておきます。 これがまとめたコミットの「土台」になります。 修正したら、エディタを保存して閉じます。 コミットメッセージを編集する エディタを閉じると、Gitがrebase処理を実行し、まとめたコミットの新しいコミットメッセージを作成するためのエディタが再度立ち上がります。 このエディタには、 squash に指定した各コミットのメッセージが自動的に結合された状態で表示されます。 # This is a combination of 3 commits. # This is the 1st commit message: chore: add second line # This is the 2nd commit message: refactor: improve wording # This is the 3rd commit message: feat: add final sentence # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch main # Changes to be committed: # modified: README.md # この結合されたメッセージを編集し、まとめたコミットとして適切なコミットメッセージに書き換えます。 不要な行(特に # で始まるコメント行や、元のコミットメッセージのヘッダー部分)は削除し、最終的なコミットメッセージだけを残しましょう。 例: feat: Add second, third, and final lines to README メッセージを編集したら、エディタを保存して閉じます。 結果を確認する エディタを閉じると、rebase処理が完了します。 念のため、 git log --oneline コマンドでコミット履歴を確認してみましょう。 git log --oneline 最新の3つのコミットが一つにまとまり、先ほど編集した新しいコミットメッセージになっていることが確認できるはずです。元の3つのコミットは履歴から消えています。 <新しいコミットのハッシュ> feat: Add second, third, and final lines to README <最初のハッシュ> feat: add initial README file これで、複数のコミットを一つにまとめることができました! おわりに git rebase -i の squash オプションを使うことで、開発途中で増えた細かなコミットを一つにまとめることができます。これは、コミット履歴を綺麗に保ち、プルリクエストのレビューを効率化するために非常に役立ちます。 git rebase -i <対象の範囲> でインタラクティブrebaseを開始する。 エディタで、まとめたいコミットの pick を s (squash) に変更する。 一番上のコミット(まとめる先)は pick のままにする。 次に表示されるエディタで、まとめたコミットの新しいメッセージを作成する。 適切に使えば、Gitワークフローがより洗練されるはずです。ぜひ試してみてください!
アバター
こんにちは!ニフティの島、江田、坂内です。 2025年6月1日(日)に開催される「技術書典18」に出展することになりました! NIFTY Tech Day2025で来場者にお配りしたニフティの技術書「NIFTY Tech Book #2」を無料頒布いたします! イベント概要 イベント名: 技術書典18 日時: 2025年6月1日(日) 11:00~17:00 場所: 池袋・サンシャインシティ 展示ホールD(文化会館ビル2F) 参加:入場無料 ブース:「さ17」 ( https://x.com/techbookfest/status/1905070279279456395 ) 詳細は下記公式サイトをご覧ください。 https://techbookfest.org/event/tbf18 社員の「好き」と「学び」が詰まった、珠玉の9章立て! 今回頒布する技術書は、ニフティエンジニアの有志が「書きたい!」と思ったことを自由にテーマにして執筆した技術書です。その結果、総ページ数 144ページ という大ボリュームになりました! 気になる内容は、こちらの9章立てです! 1章:元SIer系SEがニフティでSREになって2年間で得た成長と学び 2章:ニフティのインナーソース普及活動 3章:VyOSを使った自宅DNSのご紹介 4章:AITuberを作ろう 5章:新人エンジニアがGitの仕組みについて調べてみた 6章:Azure Functions徹底解剖(おまけ:日次課金通知アプリのサンプル) 7章:爆速でドメイン知識を獲得してチームにジョインする方法 8章:私のTerraformプラクティス2025 9章:メタバース×業務効率化 – バイタルデータによる実務適性の定量的検証 SREのリアルな経験談から、インナーソース活動、自宅インフラ、流行のAITuber制作、Gitの深掘り、Azure Functionsの実践、チームへの高速キャッチアップ術、Terraformの最新プラクティス、そしてメタバースを活用した業務効率化検討まで…多岐にわたるラインナップです! きっと皆さんの知的好奇心を刺激する章が見つかるはずです。 表紙も手作り!そして、なんと…無料です! そして本文だけでなく表紙と背表紙のデザインも当社のエンジニアが手がけています!細部にまでこだわりと愛情を込めて作成しましたので、ぜひその点にも注目してみてください。 これだけのボリュームと内容でありながら、なんと 無料 で頒布させていただきます! ぜひ、私たちのブースへお越しください! イベント当日は、執筆やデザインや表紙制作をした社員がブースに立つ予定です。 皆さんと会場でお会いできることを、社員一同、心から楽しみにしています。 ぜひ、私たちのブースにお立ち寄りいただき、熱い想いが詰まった技術書をお手に取ってみてください!
アバター
弊社では業務PCとしてノートPCが支給されますが、外付けキーボードやマウスを利用したい方は追加支給してもらうこともできます。ですが自分好みのキーボード・マウスを利用したい方は、各人の責任で持ち込んで利用することも認められています。 その流れで私は自作したキーボードを業務PCに接続して使っていますが、そのキーボードを作成した際の知見を少しまとめました。 小型試作基板から始まったハーフキーボード作り いつも基板製造でお世話になっている JLCPCB 様は、一度の注文は最低5枚からの注文となりますが、小さいサイズの基板を安価なディスカウント価格で試作してくださいます。 回路のテストを小さいサイズで繰り返し試せるため便利に利用しているのですが、ディスカウントの範囲で実用できる物を作れないかと検討した結果、縦横5キーずつのマクロパッドやテンキーパッドに似たものを作りました。 基板が届き組み立てたところで、ふと通常文字のキーキャップをはめてみたところ、2台あればキーの数も50個になるので通常のキーボードとして使えるのでないか?と思ってしまったところから今回の話が始まります。 半分のキーボードを常用するために 届いた5枚の基板のうち2枚を使って組み立てたキーボードが2台できあがり、左手用と右手用のキーキャップを装着して役割に対応する設定(キーマップ)を書き込みます。 こうして同一回路の基板でそれぞれ対応するキーマップに替えて使うことでキーボード一台分の種類の文字を扱えるようにはなるのですが、このキーマップをそれぞれの個体にはめたキーキャップに対応するものに変えるために qmk_firmware では通常、 違うキーマップのファームウェアバイナリを作って書き込む。 それぞれをレイヤとして定義して一時的に切り替える。 via や Remap , vial といったツールで動的に書き換える。 といったいくつかの方法があります。 1.の方法ではキーマップの割り当てが電源を切っても永続化する一方で、ファームウェアを修正して入れ替えるたびに都度それぞれビルドして対応するバイナリを書き込む必要があります。 2.の方法ではPCに繋ぎなおしたり電源を入れるたびに設定を変更する必要があります。 3.の方法は上記課題をクリアできますが、ファームウェアを修正すると初期化してしまい、その都度キーマップを作り直すことになります。 この手間を解消するため、2.の方法でありながら電源投入時の初期のレイヤを固定して3.のように扱うことができるようにしたいと思います。 qmk_firmwareのBootMagic機能 話は変わりますが、qmkでファームウェアのバイナリを変更するときには、マイコン基板やキーボード基板のボタンを押して書き込みモードにする方法の他に、書き込みモードに移る内部キーコードと、PCに接続するなどの起動時に特定のキーを押しておく BootMagic という機能があります。 このBootMagicの操作方法は半固定の永続設定の操作方法としてあっているのではないでしょうか?と言うことで調べました。 bootmagicの設定例(keyboard.json) "bootmagic":{ "matrix": [5,0] }, qmk_firmwareは背後にqmkの元になった tmk やマイコンの基本API(rp2040では ChibiOS )がありますが、qmkとしてはquantum/main.cの main()関数 がプログラムの根幹になっています。 プログラム実行直後に各種の初期化をおこなった後にメインループを回す構造ですが、初期化の中でBootMagicを判断しているのはquantum/keyboard.c の quantum_init() から bootmagic.c の  bootmagic() を呼び出す流れの部分でした。 BootMagicを呼び出す流れの途中の関数には (weak) を宣言している関数があり差し替えることはできますが、通常の処理の流れを書き換えたくないため、 quantum_init() の中でファームウェア開発者に提供され呼び出されている keyboard_post_init_user() を実装します。 config.hで追加した定義 // 起動時のデフォルトキーマップ変更 #define BOOTMAGIC_L_ROW 2 #define BOOTMAGIC_L_COLUMN 0 #define BOOTMAGIC_R_ROW 3 #define BOOTMAGIC_R_COLUMN 0 #define BOOTMAGIC_N_ROW 4 #define BOOTMAGIC_N_COLUMN 0 特定のキーが押されているか確認するにはマトリクスのrow,colを指定して確認しますが、これはキーボードの基板の配線ごとに変わるところです。今回は左端上から2~4個目を押しながらUSBケーブルを接続するとキーマップが変わるように、それらのキーの配線を指定しています。 起動時に押すキーと役割(今回の例 keymap.cで記述する関数定義 // 起動時に特殊キーを押しているとデフォルトレイヤを変更する void keyboard_post_init_user(void) { uint8_t row = BOOTMAGIC_N_ROW; uint8_t col = BOOTMAGIC_N_COLUMN; matrix_scan(); if( matrix_get_row(row) & (1 << col)) { set_single_persistent_default_layer(_NPAD); } row = BOOTMAGIC_L_ROW; col = BOOTMAGIC_L_COLUMN; if( matrix_get_row(row) & (1 << col)) { set_single_persistent_default_layer(_LPAD); } row = BOOTMAGIC_R_ROW; col = BOOTMAGIC_R_COLUMN; if( matrix_get_row(row) & (1 << col)) { set_single_persistent_default_layer(_RPAD); } } 起動時に押していてほしいキーの情報は他の定義と同じように config.h などで定義することとして、一つの動作のキーごとに row(行) と col(列) を BOOTMAGIC_N_ROW, BOOTMAGIC_N_COLUMN などと定義しておき、これらのキーが現在のマトリクスマップの押下状況と比較してキーが押されているかどうかを、例では3回(左手、右手、テンキーの3種)判断しています。 マトリクスの押下判断手段は bootmagic.c の手法 に沿った方法でmatrix_get_row()で1行分の状態を取得して、colの位置のビット状態を確認します。 config.h で定義したキーが押されていると判断した場合に、 set_single_persistent_default_layer() を使って現在のデフォルトレイヤを変更すると共に、eepromに書き込んでデフォルトキーマップの変更を永続化しています。 このようにしてキーボードをくみ上げた後にキーキャップで決まる役割、それぞれに応じたキーマップに変更しつつ、その変更をeepromに記録して電源を入れなおしてもその役割を維持できるようになりました。 複数のキーボードを組み合わせて使うことについて このような経緯を経て、左右2台のキーボードを使うことでキーボード一台分の入力ができるようになりましたが、分割キーボードに比べると下記の違いがあります PCに接続するためのUSBポートが2口必要(Hubでも可) qmk機能が個々のキーボードに閉じるため、レイヤキーなどの操作が両手に分担できない(左でレイヤキーを押しても右のマップが変わらないなど) 同上の意味ですがRGBマトリクスなどエフェクトが左右別になる 片側だけでも動作・利用可能、途中で抜き差し可能 消費電力はそれぞれ別の上限になるため余裕がある これらの違いは、キーボードの種類と利用者それぞれの設定状況(キーマップや利用方法など)で受容できるかどうかが決まりますので、その時々の判断が必要となります。 とはいえ、個人的にはこの状況でキーの少なさ以外の不自由を感じずに日常業務で利用できていますので、変わり種の多い自作キーボードのバリエーションの一つとしていかがでしょうか。 おわりに 自作キーボードのファームウェアはソフトウェアと電子回路、機械的な事柄まで幅広い知識が身に着けられるのでソフトウェアエンジニアとして幅を広げるにはいい課題ではないでしょうか。 キーボードと言う毎日使うデバイスですので、活動の結果がQOLに直結する点も素晴らしいと思います。 この記事で興味を持って一人でも多くの仲間が現れることを期待しています。
アバター
弊社では業務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環境リプレイスを担当させていただいたことで、構築を行った事や、今まででは経験してこなかった事を経験できたことは今後にも活かせるのではないかと思っています。 属人化解消に向けて 属人化はいつの時でも出てくる課題になるかとは思います。気にしたうえで、設計及び、詳細設定にインストールしたアプリの設定詳細、データを残しておいていたとしても、きちんと内部で共有しておかないと、今後対応に携わった人が元の設定は何だったのか不明の状況が出てくるかと思います。なかなか構築を行いつつ、文書化しつつ共有を行っていく事は大変かと思いますが、どのような設定を行ったのか、インストール実行時におけるパラメータの設定は何を設定したのか詳細に記しておかなかければ、構築時はある程度記憶しているものですが、当時はどうだったのか、時間が経つにつれ朧げになっていくものかと思います。そのためには仕様だけではなく、詳細に設定した項目もきちんと残しておかなければならないと考えています。前職の経験を活かし、ドキュメントも統一性をもって作成することで、誰が見ても分かりやすい対応手順の作成を心がけを行い、チームのメンバーとは定期的に対応した内容や、設定項目の共有を行い、属人化解消に向けドキュメントの整備を続けています。 最後に 前職では運用の業務や、オペレーションを行っていましたが、ニフティに入社してからは、ドキュメント以外にも構築の分野であったり、リプレイス対応や新たな業務にも挑戦しているので、まだまだ未熟なところは多々ありますが、その中で新たな学びを得ることで今後も成長出来たらと思います。 ミス対策のなぜなぜ分析については結構有名なものですので、ご興味のある方は情報も沢山ありますので、調べていただくと良いかなと思います。
アバター