スクラム採用をチームに取り入れてみた話

この記事はfreee Developers Advent Calendar 2021の22日目です。

こんにちは。freee申告を開発するチームのマネージャーをしていますizumiです。

今日はその申告ヨットで取り組んでいる「スクラム採用」についてお話したいと思います。

スクラム採用とは?

「スクラム採用」でググるとたくさんの記事や広告が表示されます。その内容をみていくと、「採用チームだけでなく現場の社員達も一緒に主体的に採用を行うこと」で採用成果を創出していくための手法で、株式会社HERPさんが提唱されているようです。

その定義に当てはめるのであれば、freeeは創業したときから今までずっとスクラム採用をやってきました。

採用チームだけでなくエンジニアも自分達で候補者を探し、採用チームとエンジニアマネージャーでデータをみて振り返り、採用面接やカジュアル面談なども多くのエンジニアが皆で取り組んできました。

今回私たちのチームで取り組んだのはそうではなく、普段開発プロセスとして採用しているスクラム開発手法をしっかりと採用にも取り入れてチームで取り組んでみるというもので、実際に今年の7月から実施しています。私はこれをfreee版スクラム採用と呼んでいます。

freee版スクラム採用に取り組んだ背景

freeeに限らず成長企業であればどこでも同じだとは思いますが、多くの優秀なエンジニアを採用することは会社の成長スピードを加速させるためにも、自分達が目指すプロダクトビジョンを達成するためにも非常に重要です。

しかし、エンジニア採用市場の厳しさと毎年増え続ける採用計画により、会社の規模が大きくなるからといって採用の難易度は下がるわけではなく、むしろ採用ハードルを下げずにカルチャーマッチする人材を採用し続ける難易度は高くなっている感覚があります。

申告チームでもなかなか採用が進まずに苦しんでいました。(今も苦しんではいますが) 採用エージェントの方への説明、社員への紹介依頼、そして採用媒体に載っている人で話をしてみたい人をピックアップしてスカウト送信するなど日々取り組んではいるものの、どうしても普段の業務が忙しいなかでやらないとはいけないとは思いつつも、面接・面談以外の採用活動は後回しになりがちで、チームとして主な活動となる採用候補者の検索とスカウトに関しても、採用計画から逆算した必要なスカウト数や面談数を確保できずにいる状態でした。

採用ももちろん大事ですが、エンジニアの本業はプロダクトを開発することです。採用を強制感なくチームで楽しみながら進めていきつつ、ただ採用に使う時間を増やすことでリカバリをする以外に良い方法はないものかと悩んでいました。

そんな中、エンジニアではないfreeeのあるチームで採用がうまく進んだのでその秘訣を聞くと、採用隊長が採用チームと毎週振り返りを実施してボトルネックの洗い出しや対策を行うこと、そして数字や情報の共有をしっかりやるというものでした。

振り返りと状況の見える化の旗振り役。これってなんだかスクラムマスターみたいだなと感じたところから、開発チームの採用活動に本格的にスクラム開発手法を取り入れてみてはどうだろうかと思ったのがこの取り組みを実施するに至った背景です。

スクラム採用での役割

今回実施しているスクラム採用プロセスでは、役割は明確には定義しませんでした。

例えばプロダクトオーナーに該当する採用オーナーのような役割を定義することも最初は考えましたが、実際には採用という明確なゴールがあるので細かな意思決定や調整は必要ないこと、そしてスクラム採用に参加するチームメンバー自身が面接官でもあり、それぞれが採用の決定権を持っていることなどもあり特に明確にしないことにしました。

採用スクラムマスターに関しては私がやっています。実際にはプロセスの設計やファシリテーション、やっていきの表明などは開発マネージャーでもある私が行い、スクラムイベントで参照するためのデータの整理やプランニングの準備は採用チームが行っています。

今後よりプロセスが慣れてきたタイミングで、役割を明確にして開発プロセスでのスクラムマスターの疑似体験をエンジニアメンバーが経験するなども面白いなとは考えています。

スクラム採用イベント

スクラムイベントとして採用したのは、スプリントプランニング、スプリントレトロスペクティブ、スタンドアップミーティングの3つです。

採用デイリースクラム(朝会)

採用デイリースクラムは簡易的なものです。 コロナでリモートワークが中心というのもありますが、slackでやること・やったこと・気づきや困ったことなどを共有しています。

プロダクト開発のための、本当のデイリースクラムミーティングも毎朝行っていますので、採用のデイリースクラムは簡易的なものにしています。もしこれがコロナではなく全員出社していたとしてもslackでのやりとり程度にしていると思います。

また、開発チームの主要な活動である、採用媒体から話をしてみたい人を検索してピックアップした件数を毎朝Slack botが投稿するようにして状況の可視化をしています。 仕組みはすごく簡単で、スカウト候補者をGoogle Spreadsheetで管理しているためGoogle App Scriptを使って毎朝10~11時の間にslackに投稿するようなスクリプトを用意しています。

botがメンバーごとのスカウト数を投稿しているSlackのスクリーンショット

採用スプリントレトロスペクティブ(振り返り)

毎週金曜日にプランニングと合わせて、開発チームと採用チームが参加して実施しています。 まずは最初にデータを眺めていきます。スプリント内でのスカウト送信状況、返信状況、スカウト媒体ごとの数字、候補者のパイプライン状況、面接官ごとの面接数、辞退発生状況などなど。 そして、前スプリントで発生したTry(改善事項)の取り組み状況なども確認していきます。

少しでも楽しみながら採用活動をやれるようにと、スカウト返信王決定戦も実施しています。スカウト候補者から返信がきた場合に、その候補者を最初に見つけた人にポイントが入る制度です。 3ヶ月ごとに区切ってスカウト返信王には私から豪華景品を贈呈するということにしています。ただし、もうすぐこのスクラム採用を初めて最初の3ヶ月が経つのですが、今のところ私がスカウト返信王になるのが確実なので、豪華景品の贈呈はなくなりそうです(笑)

その後は振り返りを行います。デイリースタンドアップで出た気づきや困りごともここで拾いながら振り返ってTryを出していきます。

例えば毎回ではないですが、スカウト送信するか迷った候補者の目線合わせやペルソナ作り、返信率が高いメンバーがどういうスカウトを行っているのかを分析したり、モブプロならぬモブスカウトみたいなことをやることもあります。

十分にはまだまだできていませんが、これまでマネージャーと採用チームだけでやることが多かった候補者のペルソナ作りや募集要件決めなどを短い時間ながら全員でもやることで共通認識を高めていく効果があると期待しています。

採用スプリントプランニング(計画)

レトロスペクティブの後にそのままプランニングを行います。 媒体によっては採用チームの方でリストを用意する必要や、期間限定の媒体もあったりするので次のスプリントで誰がどういった媒体でスカウト候補者を検索するかなどを決めていきます。優先順位を確認して、アバウトなアサインを決めて終了します。

やってみてどうだったか

まだ開始してから3ヶ月というタイミングではありますが、成果には繋がっています。 スカウトがあきらかに増加し、採用候補者やカジュアル面談の件数も増えています。 とはいえ、最終目的である採用決定者の数はまだまだ満足できるような状況ではありません。

エンジニアは当然高い技術スキルとプロダクト開発において優れた能力とマインドを持った人たちですですが、freeeでは一緒に働く優秀なエンジニアを自分達で採用するんだというカルチャーができてはいます。

それでもやはり、一歩間違えば採用候補者探しや面接などをめんどくさいと思ってやりたくないとなってしまい、採用活動がエンジニアのモチベーション低下を招いてしまう危険性のあるものだとも考えています。

チームメンバーには、日々のプロダクト開発に加えてこうしたスカウトや面接などの採用活動で多くの苦労をしてもらっていますし、もしかしたら全然できてないよと怒られてしまうかもしれませんが、このスクラム採用を通じて少しでも楽しみながら、チーム全員で一緒に働きたいと思える優秀なエンジニアを採用するぞというモチベーションで採用に関わってもらいたいなと思っています。

最後に

この記事を読んでいただいたあなたにも私たちのfreee申告のスクラムチームからのスカウトメールが届くかもしれません。 ここまで採用の話を色々と書きましたが、もちろん全然転職意欲がなくてもカジュアル面談でお話しさせていただくだけでも大歓迎です。あなたの返信をお待ちしています!

明日は酒ばかり飲んでいるmatsuzawa君が担当です。お楽しみに!!

freee会計の React 17 化を進めていたら flux に PR を出していた話

この記事は freee Developers Advent Calendar 2021 の 21 日目の記事です.

freee でエンジニアをやっているけむりだま (@_kemuridama) です. freee会計の開発をメインに freee Tech Night の運営リーダーをやっていたり, オンラインになった社内イベントの企画や配信をやっていたりします.

今回は freee会計の React 17 化を進めていた際に facebook/flux のバグにハマって, 修正のために PR を出してパッチリリースをしてもらった話をしようと思います. 2018 年に新卒で入社してから毎年 Advent Calendar に参加してますが 3 年ぶり 2 回目の技術的な内容になりますw

経緯

freee会計を React 16 から 17 にアップデートするために, アップデート起因でサービスの動作のデグレが発生していないか QA を行ってもらっていました. その中でとあるページの一覧画面にある検索フォームが動かないというチケットが起票されました.

細かい動作を調べてみると検索ボタンを押した際に query string の変更は入るが再検索が何故か走らずに一覧画面のデータが更新されないという問題でした. React 17 には基本的に新機能はなく大きな破壊的変更もイベントデリゲーションに関するものだけで, 今回のようにボタンのクリックイベントをハンドリングして query string を変更するという処理は影響を受けないように見えました.

Developer Tools を使ってデバッグをしながら詳細に動作を追っていると UNSAFE_componenWillReceiveProps() が呼び出されていないということが発覚しました. これは React の class component に存在している lifecycle method というもので, 親から渡される props が更新されると呼び出されるメソッドです.

今回のページは以下のような動きで検索機能を実現してました.

  1. 検索ボタンが押されて history.push() で query string を更新
  2. React Router が変更された query string を元に component に渡す props を更新
  3. UNSAFE_componentWillReceiveProps() で props の更新を検知して検索を実行
  4. 検索結果を元に一覧画面を更新

しかし 3 の UNSAFE_componentWillReceiveProps() が実行されないので props の更新を検知できず検索が実行されないため期待した動作をしなくなってしまっていました.

UNSAFE_ という prefix がつく lifecycle method は将来的に削除される予定であることは知っていましたが, 16 から 17 へのアップデートでそれが動かなくなったのは想定外でした. 試しに他の UNSAFE_ という prefix がつく lifecycle method も動いていないのか確認しましたが正しく呼び出されているように見えました.

どうやら UNSAFE_componentWillReceiveProps() だけが呼ばれていないようだ…

React のコードを見てみる

この時点では React のアップデートがこの問題の原因だと思っていたので, React のコードを読んでみることにしました. React 17 で UNSAFE_componentWillReceiveProps() の呼び出しが記述されているのはこの部分だ.

https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L775-L805

コードを読むと callComponentWillReceiveProps() は props が変更されると呼び出され, class component に定義された componentWillReceiveProps()UNSAFE_componentWillReceiveProps()function であればそれらを呼び出すようでした.

componentWillReceiveProps()componentWillMount(), componentWillUpdate() と並んで既に deprecated になっていて, v16.3 で導入された UNSAFE_ prefix が付いたものを使うことになっています. (UNSAFE_ prefix が付いたものもレガシーと位置づけられていて新規コードで使うのは避けるべきです.) しかし React 17.0.2 時点ではまだ prefix なしの lifecycle method も使えるようになっているようです.

Developer Tools を用いて callComponentWillReceiveProps() 内に breakpoint を置いて debug してみると, 該当の component に定義されている UNSAFE_componentWillReceiveProps()function となり期待通り呼び出されているようにみえます.

呼び出されているはずなのになぜか期待通りに動かない…

flux のコードを見てみる

試しに UNSAFE_componentWillReceiveProps()componentWillReceiveProps() に書き換えてみると期待通りの挙動をするようになりました. ということは自分で定義した UNSAFE_componentWillReceiveProps() がどこかで書き換えられているのではないかと推察しました.

実は問題が起きている component は container component で export する際に Container.create() で class component を container 化していました. これは facebook/flux で定義されているメソッドで, 渡された class component を継承して flux で必要なメソッドを override しています.

debug を進めていると flux による class component の継承の中で UNSAFE_componentWillReceiveProps() も override されているようだったので, flux のコードを見てみることにしました.

https://github.com/facebook/flux/blob/4.0.0/src/container/FluxContainer.js#L151-L163

flux によって継承された class component の UNSAFE_componentWillReceiveProps() では super.componentWillReceiveProps() が存在する場合はそれを呼び出すという記述だけで, override されてしまった super.UNSAFE_coponentWillReceiveProps() は呼び出されないということが分かります.

何ということだ…😇

なぜこの問題が起きたのか

今回 React 17 化をするに当たって peerDependencies に React 17 の追加が行われている flux も v3.1.3 から v4.0.0 にアップデートしていました. v3.1.3 時点 では継承された class component は componentWillReceiveProps() を override して super.componentWillReceiveProps() が存在していたらそれを呼び出すという実装になっています. そのため UNSAFE_componentWillReceiveProps() は flux によって継承されることなく, そのまま React の lifecycle 通りに呼び出されて期待通りの挙動をすることが分かります. freee会計内の class component の deprecated な lifecycle はすべて UNSAFE_ prefix 付きのものに置換されているのでアップデート前の v3.1.3 では期待通りの挙動をしていることが証明されました.

まとめると…

flux v3.1.3

  • componentWillReceiveProps() が flux によって override され, super.componentWillReceiveProps() が存在すればそれを呼び出す
  • super.UNSAFE_componentWillReceiveProps() は flux によって override されないので React の lifecycle 内で普通に呼び出される
  • freee会計内の componentWillReceiveProps() はすべて UNSAFE_componentWillReceiveProps() に置換されているが flux で継承された際に影響を受けないので期待通り動作をする

flux v4.0.0

  • UNSAFE_componentWillReciveProps() が flux によって override され, super.componentWillReceiveProps() が存在すればそれを呼び出す
  • super.UNSAFE_componentWillReceiveProps() は flux によって override されてしまっている, かつ override したメソッドの内部でも呼び出されないので一生呼び出されない
  • freee会計内の componentWillReceiveProps() はすべて UNSAFE_componentWillReceiveProps() に置換されていて flux で継承された際に影響を受けるので期待通り動作しなくなる

flux v4.0.0 で override したメソッドと内部で呼び出す super class のメソッドが食い違ってしまったことによって起きたバグということになります.

PR を出してパッチリリースをしてもらう

解決するのは簡単で UNSAFE_componentWillReceiveProps() を override した際に super.componentWillReceiveProps() だけではなく super.UNSAFE_componentWillReceiveProps() も呼び出せば良いです. 期待通りの挙動をするように flux に PR を出しました.

github.com

flux は既にメンテナンスモードになっていますが, 無事にメンテナーによって merge されて patch release も出してもらえました, ありがたい 🙏 flux から patch release がでなければ社内で fork して patch を当ててそれを使うというところまで考えていたので本当に良かったです.

まとめ

今回は freee会計が React 17 化するに当たって直面した flux のバグとその調査と解決の話をしました. 自分は普段からフロントエンドのライブラリアップデートや技術的負債の解消などをよくやっているのですが, OSS に対して contribution をするところまでやったのはあまりなかったのでとても良い経験でした. 今後もこのような取り組みを続けていけたらなと思います.

明日は yuichiro がスクラム採用について書いてくれるみたいです, お楽しみに!!

異動をカジュアルに!freeeの社内異動制度「異動戦国」

こんにちは、最近キャリアのヒントをつかみ出したichienです。今回はfreeeの開発組織にある社内異動制度、「異動戦国」をご紹介します。異動戦国とは、1年に1回自分の希望するチームに異動の立候補をすることができる制度です。

実際に異動戦国で異動を経験した二人のエンジニアに、制度の内容や、何故異動しようと思ったか、異動してみて実際どうだったか、チームごとの技術スタックや雰囲気・働き方の違い等について話を聞きました。

インタビュー風景
インタビュー風景

ryudo:写真右上。2019年1月入社。1年半認証認可/課金基盤の開発を担当。2020年7月に異動戦国制度を利用してfreee人事労務の給与チームに異動。

kurubushi:写真左下。2019年新卒入社。入社時は認証認可基盤の開発を担当。2021年7月に異動戦国制度を利用してfreee人事労務の社宅チームに異動。

「異動戦国」とは

―異動戦国ってそもそもどんな制度なんですか?

kurubushi:どこに異動したいか自分で希望を出すことができる制度です。中途入社の人は全員、新卒入社の人は2年目から応募することができ、12月頃に案内があり希望を提出すると上長たちの間で話し合いの場が持たれ、来期の7月に向けて異動を実現してくれるという制度です。

もちろん実際に異動できるかは、その時のチームの状況や異動先でのポジションの空き次第ですが、僕の場合はその希望が通って実際に異動することができました。

Slackでの案内の様子
Slackでの案内の様子

なぜ異動しようと思ったのか

―自分の意思を伝えられて、しっかり話し合ってもらえるのはいいですね。なぜ異動しようと思ったんですか?

kurubushi:僕の場合、新卒入社は基本的に全員2年で異動するというルールがあり、それに則って希望を出しました。異動希望の提出期間が始まるときに、各チームを紹介するランチ会があったのでそれに出て具体的な業務の情報やどんな人がいるかをキャッチアップしました。あとは毎Q各チームの成果発表会があるのですが、それを見てどのチームがどんなプロダクトを作っているのかを確認しました。

もともと所属していた基盤チームでは開発全体に大きなインパクトのあるプロジェクトにじっくり向き合うことが多かったんですが、そうやって話を聞いている中で、いろんな人と関わりながらユーザーと近い距離で新しいものを作るようなことにもチャレンジしてみたいと思うようになりました。具体的にどのチームに異動したいかは決まっていなかったのですが、当時のJM(freeeではマネージャーのことをJMと呼んでいます)に相談したところ、希望が叶いそうなチームをいくつか紹介してくれて、最終的に社宅チームに異動することになりました。

―2年で異動って結構早い印象ですが、不安ややり残したことはなかったですか?

kurubushi:自分としては飽き性な性格もあるので(笑)2年はちょうどいいタイミングだと思っています。2年で基本的に全員異動なのですが、異動のタイミングはかなり柔軟です。当時、認証認可基盤の開発が伸びていて、JMに最後までやり切りたいという話をしたら、異動のタイミングを伸ばしてくれました。

会社にいる風の背景をweb会議で設定するのにハマっているkurubushiさん
会社にいる風の背景をweb会議で設定するのにハマっているkurubushiさん

ryudo:私のときもそうで、やり切りたいか、異動したいかみたいな気持ちは本人に聞いてくれるし、その意向に合わせて柔軟に対応してくれましたね。

―ryudoさんはどのように異動先を決めたんですか?

ryudo:もともと課金基盤の立ち上げのプロジェクトに基盤側のエンジニアとして参加していたんですが、その新課金基盤で稼働するプロダクトがfreee人事労務でした。なので、元々人事労務側のエンジニアと関わることが多く、心理的な距離感が近かったというのがありました。

基盤チームの場合、裏側からサービスを支えることが多いので、freeeの開発全体に習熟することができました。次のステップとして、もっと幅広く知りたいと思ったときに、やりたいこととこれまでに縁があった人々が組み合わさってfreee人事労務のチームに異動してみようと思いました。当時のJMも合うのではないかと背中を押してくれました。

―異動するとなって周りの反応はどうでしたか?

kurubushi:ryudoさんが異動してしまうと知って寂しかったです(笑)が、全員で頑張って引継ぎをして無事に送り出すことができました。

ryudo:発表から異動までは3ヶ月くらいはあるので引継ぎは割と余裕がありました。このタイミングから異動!と明確に決まっているわけではなく、kurubushiさんもそうでしたが、いまのプロジェクトの切りが良いところまでやってから異動する形なので、そこも柔軟で良いと思います。

kurubushi:そうすることで、お互いの組織にwin-winな関係になれていますよね。いい関係性が作れているので、僕はいまでも基盤チームの飲み会に参加してます(笑)

ryudo:当時は特にfreeeの開発組織の中で人材の流動性を高めるためにもっとカジュアルに異動できるようにしたいよねという話があって、私も希望を出してみました。freeeを代表するようなシニアエンジニアも異動すると知って、「え、あの人も異動するの!?」と驚きましたね。お祭りのような感じで盛り上がっていたと思います。

―ネーミングが「異動戦国」ですもんね(笑)聞いたときは戦うのかと思っていました(笑)

ryudo:その当時は天下統一プロジェクトみたいな名前のプロジェクトもあって、なぜか戦国味が強い時代でした(笑)

宅飲みでワインに合うつまみを試行錯誤するのにハマっているryudoさん
宅飲みでワインに合うつまみを試行錯誤するのにハマっているryudoさん

異動前に感じた人間関係や技術面での不安

―異動することに不安はなかったですか?

kurubushi:不安はちょっとありました。新しいものに触れることにワクワクする気持ちも大きかったですが、リモートワークが始まっていたので、ちらっとチームの雰囲気を見に行くこともできず、人となりが分からなかったのは不安ではありました。

ryudo:異動先について面識はあったけど実際どんなチームなのかは知らなかったですし、ドメイン知識もなかったです。何をやっているのか、いままでやってきたことが役に立つのか、自分がチームで通用するのかが分からなくて、特に異動直前は技術的な不安と人間関係の不安の両方がありました。

ですが、実際に異動してみると、freeeは基本いい人が多いので(笑)人間的な不安はすぐに解消されましたね。

技術的な不安についても、ドメイン知識がなくても多少貢献できるものはあったし、アンラーニングしてやれたのも良かったです。

技術スタックやドメイン知識は異動してから十分習得できる

―技術スタックは変わったんですか?

ryudo:特に変わっていないです。他社なら製品ごとに全然違う言語、フレームワークっていうパターンはけっこうあると思うんですが、freeeは標準化に力を入れてるのであまりそういったことがなくて、技術面での異動の障壁は低いと思います。もともとfreeeはアプリエンジニアでも基盤を触ったりすることがあるので、他チームがやっていることをちょっとだけ分かる状態になっています。チームのカラーは違うけど技術は地続きになっているので、そこは安心して異動しやすいベースになっていると思います。Vibesもその一例です。

kurubushi:僕も技術スタックはそんなに変わらなかったです。もともと認証認可基盤ではGoを使って書いてたので、異動することになってGoからRailsに戻った感じでした。

新しいコード、新しいサービス、どういう機能がどういうふうに実装されているのかという面白さはありましたね。基盤と比べて、アプリ開発は機能リリースまでが早いというのが特徴的だと思いました。基盤は3年くらいかけてコツコツやっていくプロジェクトが多いのに対して、プロダクト側はとりあえず出してみるという環境だったので、小さくたくさん作っていけるのは面白いと思いました。そういう回し方もあるんだなというのが分かりました。

―ドメインの理解にはハードルはなかったですか?

ryudo:異動前は不安だったんですが、他エンジニアも学びながら作っているのでそこに不安を感じなくて大丈夫ということが分かりました。コードが読めればそれを足がかりにドメイン理解はできていくので、異動前に思ってたほど苦労や辛いと感じたことはなかったですね。

あとは給与計算関連の本を読んだり、メンバー全員での勉強会に出たりしています。ドメイン知識がまだまだ薄いという認識が全員にあるので学ばなきゃいけない。逆に言うといまできなくても全然大丈夫で、一緒に学んでいけばいいので、採用面接でも候補者に自信を持ってそのように伝えています。

社内でもチームの雰囲気や働き方はけっこう違って面白い

―チームの雰囲気や働き方にはどんな違いがありましたか?

ryudo:基盤・コアプロダクト・新規プロダクトの3つで、スピードと人物タイプが大きく分かれるような気がしています。

基盤みたいにコツコツ改善していくチームは、一発で大規模障害みたいな緊張感がずっとあるので、だからこそ深く見ることが重要です。人物タイプはハッカー気質な人が多いイメージです。

freee人事労務やfreee会計のようなコアプロダクトは動きは早いほうだと思うものの、大きく育っているプロダクトなので慎重にやらないといけないし、いまのプロダクトベースでものを考える感じです。ハッカー気質な人はあまりいなくて、関係があるか分かりませんが、人事労務の人はみんなでキャンプに行ったりしています(笑)

一方で新規プロダクトチームは何もないところから試行錯誤して考えること、スピードが求められるように思います。

採用面接でもその3タイプのどこに当てはまりそうかな?と考えたりします。

kurubushi:たしかに、基盤チームは安全感のあるチャレンジをしていく感じですね。認証認可基盤の場合、プロジェクトの性質上エンジニアの裁量が比較的大きく、PMを兼任しているようなイメージでした。一方プロダクトチームの場合はPM、セールス、エンジニア、QAがひとつのチームになっているのが驚きでした。役割が切り分けられていて、それぞれの領域のプロが一緒になって作っていくというのが新鮮でしたね。

ryudo:デザイナーやPMみたいな人たちと間近で仕事して、そういう人たちの発言や振る舞い、考え方を知れるのはいいですね。「みんなでモノづくりする」というということの体感も得られました。基盤チームはエンジニアが主役でエンジニアが中心で意思決定するという感じはありますよね。プロダクトチームのエンジニアはひとつの役割として存在している感じ。自分で作りたいのかチームでやりたいのかによってどっちが向いているのかは変わると思います。どっちも学べることがあって、一つの会社で両方体験できるのはいいですね。

プロダクトチームは品質ももちろんですが機能をいかに早く出すかということが重要な場面も多いので急いでやることが多いですね。ユーザーからの日々の要望やJIRA対応などもあるので慌ただしいです。一方で、基盤チームは失敗することができないので、急ぐのではなく慎重さが求められます。社内でもチームによってタイプもスピードも違うし、全然別の楽しみ方ができます。

カジュアルに異動できてキャリアも広げやすい

―次チャレンジしてみたい領域はありますか?

ryudo:いまはまだ学ぶことが多いので考えていないのですが、基盤・コアプロダクト・新規プロダクトの3つの領域で考えたときに、いずれ新規プロダクトのスピード感は1回味わってみたいと思ってます。

エンジニアって同じことずっとやってるとこれで大丈夫なのか、これじゃ通用しなくなるんじゃないかという不安が出てくると思うんですよね。それが理由で転職していくと思うんですけど、カジュアルな異動制度があることによって、ある程度キャリアの行き詰まりを解消できると思っています。

おおっぴらに相談できるし、異動の根回しみたいなものもいらないです。 多くの人はそれに疲弊するから転職しようとなると思うんですが、freeeの場合はそれがないです。転職の代わりになるし、気分が変わって刺激を受けながらまたフレッシュに働けるのがいいところだと思っています。

kurubushi:いまはまだ異動したばかりなので考えていませんが、技術スタックの違いがないので、新規プロダクトで新しい技術にも挑戦できるといいなと思ってます。これがやりたい、あれがやりたいっていうのが絞れてない人って多いと思うんですが、例えばセキュリティプロを目指してるとか、IDまわりをやりたいとかって思ったときに、幅広くやってる会社の中で気になるところにぴょんぴょん行っていろんな経験ができるのはすごくいいと思っています。

ユーザーと接したいと思えばコアプロダクトのチームにいけばいいし、また基盤チームに戻りたくなったら強くなって戻ってくることもできます。freeeに入ってからも選択肢はいっぱいあって、マジ価値の実現のためになんでもやれる環境があるのはすごくいいと思ってます。

―なるほど、プロダクトの幅もキャリアの幅も、入社後でもいろんなチャレンジができる環境があるのはいいですね。異動戦国、今年はどうなるのかとても楽しみです!!

Argo CD & Rollouts を使って freee会計に canary release を導入しました!!

こんにちは、SRE で主に CI/CD 周りを担当している kumashun です。
この記事は freee Developers Advent Calendar 2021 20日目の記事になります。

私は今年11月に開催された、クラウドネイティブに関する国内でも大規模なテックカンファレンスである CloudNative Days Tokyo 2021 にオンライン登壇をしました。(人生初の社外登壇でもありました。)

speakerdeck.com

event.cloudnativedays.jp

登壇内容はタイトルの通りで、freee の中でも最大規模のサービスである freee会計に、Argo CD 及び Argo Rollouts を使って canary release を導入するまでの過程を述べています。コメントやアフタートークなどで反響を頂いたものの、登壇時点では Argo Rollouts の本番導入には至らなかったため、TBD な内容が多かったのが心残りでした。

しかし、一ヶ月後の12月中旬に満を辞して Argo Rollouts が本番環境に導入され、freee では初となる Kubernetes 環境での canary release が実装されました。

チームでの canary release 導入祝勝会の様子
チームでの canary release 導入祝勝会の様子

この記事では、登壇時点の導入方針から本番運用に載せるまでの過程で変更/追加された項目について説明します。なお、Argo Projects の概要や freee の Kubernetes cluster 戦略などは登壇スライドをご参照くださいm

Argo Rollouts の導入手順

導入手順の詳細はスライドにもあるので簡単におさらいすると、

  • kind: Deployment から kind: Rollout への移行
  • kind: Rollout 向けRBAC の追加
  • stable/canary ごとの bug や metrics の出し分け設定

となっています。

kind: Deployment から kind: Rollout への移行

Argo Rollouts 導入前後のインフラ構成
Argo Rollouts 導入前後のインフラ構成

freee では Helm/Helmfile を使った manifest 管理を行っており、サービス共通で利用できる common chart を運用しています。その common chart の values にオプションを渡すことで、Deployment の代わりに Rollout を使えるようにしました。

本番環境では以下の手順を踏むことで、ダウンタイムを発生させることなく移行を完了させています。(Deployment と Rollout が共存する間は、Service に紐付く Endpoints の中には両方の Pod が追加されていたので、常に Pod がリクエストを受けられる状態になっていることが確認できました。)

  1. chart version をあげて Deployment => Rollout にする PR をマージ & prune: false で Sync
  2. Rollout が作成されるが、Deployment もそのまま残る。
  3. Rollout の Pod が全て Running になったことを確認後prune: true で Sync し、Deployment を削除する。

prune は、manifest 上からは削除したが、実態が残っているリソースを削除するかどうかを選択できる Argo CD の Sync オプションです。

kind: Rollout 向けRBAC の追加

登壇時は Argo CD の GUI 上から Rollout に対する操作を行うための RBAC 追加の説明をしましたが、後述する CLI での操作のために以下のような RBAC も追加しました。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: argo-rollouts-clusterrole
rules:
- apiGroups:
  - argoproj.io
  resources:
  - rollouts/status
  verbs:
  - patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: argo-rollouts-crb
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: argo-rollouts-clusterrole
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: hoge-group

stable/canary ごとの bug や metrics の出し分け設定

DataDog での metrics や log の出し分けには、スライド通り Ephemeral Metadata を使用しました。加えて、Deployment で watch していた metrics は、Argo Rollouts controller が提供している metrics を Datadog openmetrics integrationで取得しています。

github.com

Deployment で取得できていた全ての metrics が Rollout でも取得できるわけではないですが、従来の monitoring の観点から大きなデグレは今のところありません。

Bugsnag での bug の出し分けについては、登壇時点では Downward API で環境変数 = Bugsnag 上の stage を書き換える手法を考えていました。しかしこの手順ではcanary release 終了後に再度デプロイを行う必要があり、デプロイ時間が増えてしまうのは避けたいということで、Bugsnag の version を利用する方法にシフトしました。

以下の仕組みによって、canary/stable の Pod ごとに version が分かれるようにしています。

  1. Docker image build の際に、commit hash をファイルに吐き出す。
  2. initializer の中で 1. で作ったファイルの中身を app_version としてセットする。

この仕組みは canary release を導入していないサービスでも、bug とリリースの対応付けが可能になる良い機能追加となりました。

要検証な課題について

TBD となっていた以下の課題についても進捗を共有します。

  • envoy proxy の設定変更
  • kind: Application レベルで canary を進められるか
  • canary をスキップしてデプロイしたい場合の対応

envoy proxy の設定変更

freee の Cluster が外部からのリクエストを受ける routing は ELB -> envoy proxy -> Service -> Pod となっています。service 以下の構成を変更するだけなので、現状の envoy proxy の設定変更は必要ない認識でしたが、要検証となっていました。

実際これは杞憂で、検証環境で問題なくリクエストを受けられることが確認できました。

kind: Application レベルで canary を進められるか

Argo CD の GUI から Rollout の promote (canary step を進める) や abort (canary step を全て rollback する) の操作を行うことは可能ですが、それを一つずつの Rollout に対してポチポチするのは楽ではありません。ましてや abort する際は canary に何か問題があった場合なので、極力少ない工数でできる必要があります。そのため、何らかの方法で、1つの Application が持つ Rollout に対して一括で操作できる方法を考えました。

promote については現状 canary step を2段階 (5% -> 30min経過 -> 自動的に 100%) にしているので、操作自体行う機会は無くなりました。

一方 abort については、kubectl からワンライナーで一括 abort する方針にしています。

canary をスキップしてデプロイしたい場合の対応

登壇時点では common chart に values として渡している canary step を上書きすれば ok としていました。しかしabort 同様、こちらも hotifx など緊急時のオペレーションになることが想定されるので、同一の方針によりワンライナーを用意しました。

ちなみに Argo CD の GUI を、 Custom Resouce を作ることで拡張できる Argo CD extensions という Custom Controller があります。

github.com

これを利用した GUI 拡張のサンプル を参考に、押すと一括 abort や promote-full してくれるボタンを自作することも頑張ればできそう! ...なのですが、現時点ではドキュメントなどがあまり充実していないので、実装はしばらく先になりそうです。

おわりに

今年中にやりきるぞ!! とチーム内でも高いモチベーションで挑み続けた結果、無事大きな障害を出すことなく canary release を本番運用に載せることができました。既にアプリエンジニアからの反響も多く、Datadog から canary release の進行具合を眺めていると達成感が得られました。

request 数から見れる canary release の進行具合。薄い青が stable、濃い青色が canary に対する request を示している。
request 数から見れる canary release の進行具合。 薄い青が stable、濃い青が canary に対する request を示している。

Argo CD に続いて Argo Rollouts の導入に成功したとはいえ、どちらもまだまだ使いこなせていない部分も多いです。ユーザーであるアプリエンジニアにとってより良い CI/CD となるように、改善を続けていきます。同時に、Argo Workflow など他の Argo projects 導入も今後のプロジェクトに盛り込んでいきたいです!

今回の canary release 導入のような Kubernetes 環境での CI/CD 開発に興味のある方を含め、freee ではエンジニア採用を実施中です!!

jobs.freee.co.jp

明日の Advent Calender 担当は @_kemuridama san です!!
freee会計の React 17 化についての記事になるそうです!! お楽しみに〜