for Startups Tech blog

for Starupsのテックブログです

Rubyを2.7.1から3.1.4にアップデートした話と3.2.2を諦めたわけ

こんにちは。2022年12月入社の石田です。STARTUP DBの主にバックエンドのエンジニアをしています。今回はSTARTUP DBのバックエンドのRubyのバージョンアップをした話をしたいと思います。

2023/03/31をもってRuby 2.7系のサポートが終了しました。当プロジェクトでは Ruby 2.7.1を使っていたため、急遽Rubyのアップデート作業が必要に。どうせアップデートが必要ならということで、可能な限り最新のバージョンまでアップデートすることになりました。

アップデートは以下のように段階を踏んで行うことにしました。

2.7.1 → 2.7.8 → 3.0.6 → 3.1.4 → 3.2.2

以下は、Rubyのアップデートをしていく中で遭遇したエラーとその対応の記録です。

Ruby 2.7.1 → Ruby 2.7.8

何事もなくアップデート完了。 動作も問題なし。

Ruby 2.7.8 → Ruby 3.0.6

URI.escapeが削除された影響

当プロジェクトでは未だにpaperclipを使っています。このpaperclipがエラーを出しました。 Ruby 3.0でURI.escapeが削除されたのですが、paperclipではまだ使われているようです。 このgemはすでにメンテナンスされていないため、他のgemに移行が必要なんですが、工数が必要なため今回は諦めてモンキーパッチを当てて対応することにしました。 以下、モンキーパッチです。こちらからお借りしました。

config/initializers/uri.rb

# frozen_string_literal: true

require 'uri'

#
# Ruby 3.0.0でURI.escapeメソッドが削除された。
# paperclipがこれに対応していないため、他のgemに移行するまでモンキーパッチを当てる。
# https://stackoverflow.com/questions/68174351/undefined-method-escape-for-urimodule
#

module URI
  class << self
    def escape(str)
      alpha = 'a-zA-Z'
      alnum = "#{alpha}\\d"
      unreserved = "\\-_.!~*'()#{alnum}"
      reserved = ';/?:@&=+$,\\[\\]'
      unsafe = Regexp.new("[^#{unreserved}#{reserved}]")
      str.gsub(unsafe) do
        us = Regexp.last_match(0)
        tmp = ''
        us.each_byte do |uc|
          tmp += format('%%%02X', uc)
        end
        tmp
      end.force_encoding(Encoding::US_ASCII)
    end
  end
end

rss gemの追加

Ruby 3.0から rssライブラリがbundled gemsになりました。これは素直にGemfileに追加することにしました。

Gemfile

gem 'rss'

elasticsearch gemのアップデート

elasticsearchが以下のようなエラーを出しました。

wrong number of arguments (given 1, expected 0)
/usr/local/bundle/gems/activerecord-6.1.6.1/lib/active_record/relation/batches.rb:128:in `find_in_batches’
/usr/local/bundle/gems/elasticsearch-model-5.1.0/lib/elasticsearch/model/adapters/active_record.rb:105:in `__find_in_batches’
/usr/local/bundle/gems/elasticsearch-model-5.1.0/lib/elasticsearch/model/importing.rb:122:in `import’

これはRuby 3.0の位置引数とキーワード引数の変更の影響のようです。 こちらのコミットで修正されていますので、このコミットが入ったバージョンまでelasticsearchをバージョンアップしました。

elasticsearch gemのダウングレード

先程、elasticsearch gemを最新までアップデートしたのですが、これが良くなかったようです。ローカルでは動作するのですが、STG環境で以下のエラーが発生しました。

Elasticsearch::UnsupportedProductError: The client noticed that the server is not a supported distribution of Elasticsearch.

当プロジェクトではSTG環境及び本番環境はAWSを使っておりElasticsearchもAWSが提供するものを使っています。

AWSはあるバージョンからElasticsearchの呼称をやめ、OpenSearchと呼び改めました。またElasticsearchはクライアントライブラリに「接続先がElasticsearchじゃないとエラーになる」修正を加えました。この変更が加えられたのがelasticsearch gemのバージョンでは7.14系になります。なのでelasticsearch gemのバージョンが7.14以上のものを使ってOpenSearchに接続しようとするとエラーが発生したんですね。これについては以下の文献などが詳しかったです。

解決方法は文献のとおりにelasticsearch gemのバージョンを14より下のバージョンを使うことです。

以下のように対応しました。

Gemfile

gem 'elasticsearch', '7.13.3'
gem 'elasticsearch-model', '7.2.1'
gem 'elasticsearch-rails', '7.2.1'

fixture_file_uploadのDEPRECATION WARNING

以下のようなWarningが出るようになりました。

DEPRECATION WARNING: Passing a path to `fixture_file_upload` relative to `fixture_path` is deprecated.

これは以下のような単純な置換で対応できます。

# before
fixture_file_upload('150x150.png', 'image/png')

# after
Rack::Test::UploadedFile.new('spec/fixtures/150x150.png', 'image/png')

Ruby 3.0.6 → Ruby 3.1.4

bundled gemsの追加

Ruby 3.1から以下のgemがbundled gemになりました。bundlerで使う場合はGemfileに明示的に指定する必要があります。

  • net-ftp 0.1.3
  • net-imap 0.2.2
  • net-pop 0.1.1
  • net-smtp 0.3.1
  • matrix 0.4.2
  • prime 0.1.2
  • debug 1.4.0

当プロジェクトでは上記のうちnet-ftpとprime以外は使っていたのでGemfileに追加しました。

psych 3系の追加

db:migrate時に以下のエラーが発生しました。

Psych::BadAlias: Unknown alias: default

これはpsychというYAML解釈用のGemの4系と3系で解釈が変わっているのが原因です。psychのバージョンを3系に固定してあげれば解消します。

Gemfile

gem 'psych', '~> 3.1'

feedjira gemのアップデート

当プロジェクトではrssでデータを収集するためにfeedjiraというgemを使っています。もともとこのgemのバージョンが2.1.4のものを使っていたのですが、Ruby 3.1に対応していなかったので最新の3.2.2に上げました。それに伴って使い方も変わったのでコード修正を行います。

# 古い書き方
feed = Feedjira::Feed.fetch_and_parse(url)

# 新しい書き方
xml = HTTParty.get(url).body
feed = Feedjira.parse(xml)

ここで問題が発生しました。一部のurlでHTTParty.get(url)をしたときに403エラーが返ってきてしまうのです。ブラウザで開いてもcurlでたたいても正常なのになぜ?調査してもちゃんとした原因はわかりませんでしたが、以下のようにheaderをつけると回避できることがわかりました。

HTTParty.get(url, headers: { 'User-Agent' => 'Chrome/58.0.3029.110' }).body

bundlerのupdate

以下のようなWarningが出るようになりました。

Calling `DidYouMean::SPELL_CHECKERS.merge!(error_name => spell_checker)' has been deprecated. Please call `DidYouMean.correct_error(error_name, spell_checker)' instead.

これはbundlerを最新バージョンにアップデートすれば出なくなります。

bundlerのアップデート方法は以下です。

bundle update --bundler

Dockerを使っている場合などでbundlerのバージョンを固定したい場合は以下のようにします。

Dockerfile

ENV BUNDLER_VERSION=2.4.12
RUN gem install bundler --no-document -v 2.4.12

config.action_mailer.delivery_jobの設定

メーラーが動作しないバグに遭遇しました。メソッドの引数の数が正しくない旨のエラーだったのですが、ソースコードを見た限りでは間違いは無さそうでした。結果としては同時に出ていた以下のWarningに対応したら正しく動作するようになりました。

DEPRECATION WARNING: Sending mail with DeliveryJob and Parameterized::DeliveryJob is deprecated and will be removed in Rails 6.2. Please use MailDeliveryJob instead. (called from block in execute at /usr/local/lib/Ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:281)

対応方法は以下の通りです。

config/application.rb

config.action_mailer.delivery_job = 'ActionMailer::MailDeliveryJob'

ただ、この修正の方法が良かったのかは正直わかりません。とりあえず動くようにはなりましたが、相変わらず使っているのはMailDeliveryJobではなくDeliveryJobなので、ここは別途対応が必要だと思います。

Ruby 3.1.4 → Ruby 3.2.3

unicornの不具合

Rubyを3.2.3にあげて、いくつかのgemをバージョンアップしたところCIも通り、ローカルでは問題なく動作するようになりました。そこでSTG環境で動作確認したのですが、問題が発生しました。一部APIが正常に動作していないようなのです。しかもrailsのログにはエラーなどは出力されていません。色々原因を探ったのですが結果としてはunicornが原因でした。railsにはエラーが出ていませんでしたが、unicornはエラーを出していたのです。

unicorn_error.log

E, [2023-04-13T17:42:39.079818 #29845] ERROR -- : app error: undefined method `=~' for 5:Integer (NoMethodError)

unicornはもうメンテナンスされていないgemです。Ruby 3.2には対応していません。そのためunicornをpumaなどに置き換えない限りRuby 3.2へのアップデートは不可能です。今回はRuby 3.2へのアップデートは諦めることにしました。

終わりに

Ruby 3.2まではアップデートできませんでしたが、もともとの目的であるメンテナンスされなくなった2.7系から脱却することは達成しました。

ここまで3週間とちょっとかかりました。

ここにたどり着くまでには、ここでは書ききれなかった困難がたくさんありました。一部を箇条書きで概要だけ残しておきます。

  • アップデート作業中にRubyのパッチバージョンのアップデートがあった
  • bundlerのアップデート作業が終わった直後にbundlerのアップデートがあった
  • bundlerの再アップデート作業が終わった直後(翌日)にまたbundlerのアップデートがあった
  • Ruby のバージョンを上げた場合はunicornをUSR2ではなくQUITで終了して立ち上げ直さなければならない仕様
  • Ruby 3.1をSTG完了にデプロイしたところMySQLで謎のLost connectionやmalformed packetが発生。しかもRuby 2.7に戻しても直らなくなった。(これはRESET QUERY CACHEを実行することで解消することがわかりました。)
  • feedjiraのエラー対応は、さも事前に対応した風に書いているが、実は本番リリース後に発覚&対応した。

数々の困難がありましたが無事にアップデートが完了できたのはチームの皆さんのおかげです。私はひたすらバグを潰していただけですが、その裏では

  • rspecで拾いきれないバグのためにQA表の作成と実施
  • ダウンタイムが発生しないリリース手順の作成と実行
  • その他トラブル対応

などの作業がありました。これは私1人では到底できないことです。チームの皆さんにはこの場を借りて感謝申し上げます。

採用

当社ではエンジニアを募集中です。ぜひ一緒に成長していきませんか。ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら

ドメイン駆動設計の中核は「Design」である。近い未来に訪れる組織変化に「DDD」は最適なソリューションになり得るのか

こんにちは、2022年4月にフォースタートアップスにジョインしたエンジニアの八巻(@hachimaki37)です。主にタレントエージェンシー支援システム(SFA/CRM)のシステム開発を担当しております。現在所属するチームでは、サーバサイド(Ruby,RoR)、フロントエンド(Vue.js)の役割を分けず、2週間のスプリントを切って開発を行なっております。

少し前から興味が湧いていたドメイン駆動設計(以下、DDDと呼ぶ)、ありがたいことに外部研修の参加を募るアナウンスがあったため、DDD Boot Campという外部研修を受講してきました。

詳細は後述しますが、きっかけは、近い未来に訪れる当社の一課題をDDDで解決できないか?と思ったことです。また私自身、DDDについて言葉や概念をなんとなく知っている程度であったため、実践に役立つ知識を養いたいという思いで参加してきました。

今回のテックブログは、近い未来に訪れる組織変化を考えながら、DDDとは何かを初め、DDD Boot Campを受講して見えた学びや見解、そしてアクションなどについて執筆していきたいと思います。

※注記:タレントエージェンシー支援システム(SFA/CRM)とは?

時より、この言葉を使用しておりますが、社内のさまざまな部門の方々が使う業務システムとご認識頂ければと思います。

DDD Boot Campとは?

本題に入る前に、DDD Boot Campとは何かについて簡単に説明します。

講師情報:和智 右桂さん

  • 独学ではなかなか全容がつかみにくいドメイン駆動設計についてわかりやすく学べる
  • ワークショップ形式での体験を通じ業務にいかせる実践力を身につける

業務フローやシナリオは多くの現場で使われていますが、漫然と書いていても、今ひとつぴんと来ない、ということになりがちです。 単に手を動かして成果物を作るだけでなく、きちんと理解してそれを共有するためにはどうすればよいのか、ドメイン駆動設計(DDD)のバイブルでもある『エリック・エヴァンスのドメイン駆動設計』(翔泳社刊)翻訳者でもある和智右桂氏を講師に迎え、DDDにヒントを得ながら、ワークショップ形式でポイントを学ぶ講座です。

引用元:https://event.shoeisha.jp/cza/ddd

私が参加した回は、参加者20名のオンライン実施。講義に参加してみて、DDDの概念を一から知れたこと、ワークショップがあったことで、知識が一人歩きせず、業務との接続イメージが沸き、全体を通して非常に学び多い時間となりました。

近い未来に訪れる変化と課題

それは、システム利用ユーザーの変化によるコミュニケーションの肥大化です。

以下、当社 2023年3月期 通期決算説明資料から情報を抜粋しています。

引用元:https://pdf.irpocket.com/C7089/bU43/fyL8/CPUq.pdf

入社から現在にかけて、タレントエージェンシー支援システム(SFA/CRM)のシステム開発を担当しておりますが、組織拡大に伴い、今後様々な問題が出てくることが想定されます。その一つが社員数純増に伴う問題です。上記資料を見てわかるように、社員数が前期末比 +51名純増であること、そして4月以降更なる純増が見込まれます。

そこで、現在もなお顕在化しつつある問題の一つが、入社比率の変化です。具体的には、業務システムに慣れてない新人の方々が社員数全体の約40%となる近い未来が見えており、生産性の低下が大きな課題となってきています。

引用元:https://pdf.irpocket.com/C7089/bU43/fyL8/CPUq.pdf

現行システムでは機能しないのか?

決してそう言うことではありません。この先を見据えた進化、それはもっと未来にフィットさせたプロダクトへの進化が必要なのではないかという事です。

あくまで個人的な考えですが、タレントエージェンシー支援システム(SFA/CRM)は、ローンチされてからすでに数年が経っております。当初のプロダクトビジョンと今向かうべきビジョンにギャップが出始めているのではないかと考えております。それもそのはずで、会社としてのフェーズ・組織デザイン(人数・体制)が変われば、システムの課題や価値も自ずと変わっていくことでしょう。

未来に向けた新たなプロダクトビジョン

実は最近、「シンプル」と「安全」をキーワードにした新しいプロダクトビジョンが定義されました。つまりそれは、次の未来へ繋げる「意志」と「価値提供」がプロダクトに吹き込まれたという事です。そして、私どものチームでは、以下テーマを設定し、開発を進める形となりました。

  • 人員数の増加(業務システムに慣れてない新人比率の上昇)に対応できる環境
  • 業務プロセスの改善(効率的な業務進行)

これらの問題と課題を鑑みた上で、方向性を決め、システム開発に繋げていく必要があります。

どう解決していくのか

とある日、Bizからアイディアを募る連絡が所属チーム内にありました。

エンジニアチームで見える、タレントエージェンシー支援システム(SFA/CRM)の改善ポイント、ブレストレベルで構いませんので、ぜひアイディアをよろしくお願いします!

的外れなアイディアも多々ありますが、いくつか考え、Bizへ提案してみました。

テーマ:情報を「探す」プロセスを改善する

数ヶ月前に情報の「集約」と「透明化」を目的とした、比較的大きいリリースが行われました。次に課題となるのは「情報伝達」だと考えました。情報を人へ伝え届けるためには、「探す → 見つける → (自身が)理解する → (論理や体系を立てる) → 伝える → (相手が)理解する → 伝わる」といったプロセスがあると考えており、そもそも「探す」行為に相当なコストがかかっている(当社ではUXリサーチを実施しており、そこで得た情報から見えてきた課題感)のではないかと考えました。

情報収集のプロセスを改善することで、業務プロセスの改善(効率的な業務進行)に繋がると考えました。

テーマ:ユビキタス言語を使う

人の増加や入社時期の違いから、コミュニケーションコストが以前に比べ、倍以上に増加することが想定されます。なぜなら、バックグラウンドが人によって異なるため、言葉の定義や理解の違いから意思疎通が困難になるからです。

つまり、本来使うべき時間的コストが失われるため、共通言語化を図り、コミュニケーションコストを極力減らしていく策が良いのではないかと考えました。

など、さまざまなアイディアを考えてみました。

うっすら見えてきた課題感と解決の光

Bizとの共通認識である課題、それはユビキタス言語(共通言語)の活用です。「共通言語が少ない、もしくは合っていないか」そういった課題をBizは抱えておりました。私どもの新たなプロダクトビジョンであるシンプルとは何か。詳細は割愛させて頂きますが、一つの意味合いには「言葉の意味整理と統一(ユビキタス言語)」という概念があります。

少し糸口が見えてきた気持ちでした。そして、その課題解決を実現する手段の一つがDDDであると考えました。ようやく、DDDの話です。

ここからは、DDDとは何かを初め、参加してみての気づきや学びについて書いていきたいと思います。(具体的な方法論は述べていません)

重要な観点

ドメイン駆動設計は、「Design」である。よく見かける〇DD(例えば、TDD, MDD, UCDD…)は、Development(開発やテスト)の話ですが、Domain-driven design(DDD)は、開発手法ではなく、デザイン手法・設計の話であることを念頭に置く必要があります。よく出会う問題とそれにうまく対処するための設計であり、将来の変更や発展性に耐え得るかというアーキテクチャ的観点が必要です。

学んだこと

大きく下記4点です。

1. ソフトウェアには、業務知識を反映させる

DDDとは、エリック・エヴァンス氏により提唱され、一言で言うと「ソフトウェアには、業務知識を反映させましょう」という概念です。具体的には、頭の中に構成している業務知識を抽象化して反映させることです。ただし注意点があり、単なる業務知識を反映させるのではなく、不要な概念や知識が省かれ、「選び抜かれている」点がポイントです。

2. 隠れた概念を見える化し、ドメインモデルに(境界線を)反映させる

「どういう業務であるか」という概念の見える化を行う手法を様々知ることができました。例えば、業務フロー図、ユースケース図、ロバストネス図などです。ここでのポイントは、選び抜かれた業務知識の業務構造に関する理解を、「そのままソフトウェアで表現する」ことです。 業務知識を表現することで、業務理解とプログラム設計を直接的に関連づけられることで、プログラムがわかりやすく整理させ、ソフトウェアを柔軟に拡張したり、安全な変更を可能とします。

3. 1と2を実現するには、ユビキタス言語(共通言語)の認識と理解が重要である

Devだけで、完結するものではありません。Bizが選び抜いた業務知識を反映するだけでは物足りません。DevとBiz、双方がドメインモデル(選び抜かれた業務知識)への共通認識と理解が重要なのです。そのためにはまず、業務フローの中で使われている言葉の定義と認識を合わせることが、ファーストステップとして必要です。

4. 大切という感覚は、抽象化した自分の解釈における価値観である

言葉スケッチというアイスブレイクを冒頭に行いました。1対Nの関係で、1がお題となる写真を言葉だけでNに伝え、その情報を元にNがスケッチするといった内容です。私は、Nの立場で参加しましたが、思いの外難しい。1の立場からすると、そもそも描かれている全ての情報を説明することは困難であり、目に映るモノの中で、大切だと思うモノを自身で選択して説明しています。つまり、伝えている情報は、そのモノ自体ではなく、自分の理解(=モデル)なのです。

ユビキタス言語の認識と理解を合わせるためには、言葉だけのコミュニケーションではなく、上記で紹介した〇〇図などを使い、双方の概念やモデルの見える化を行い、共通のイメージを合わせる過程が必要です。どの言葉を選択し、どのように相手の頭の中を整理しながらモノごとを伝えていくのか、このような「言葉で作りたいモノを伝える」ことは非常に重要なスキルです。

実践してみようと思うこと

1. 言葉の意味整理と統一(ユビキタス言語)

ここは欠かせないと感じました。DDDをやるやらないに関わらず、言葉の共通理解の重要性を改めて感じました。まずはチーム内から、そしてユーザーとのコミュニケーションをユビキタス言語で図れる世界に近づけるよう、ファーストステップを踏んでいきたいと思います。

2. 業務フロー図、ユースケース図、ロバストネス図、ホワイトボードなどを使って、図でイメージを伝える

私自身、(例えばロジックなど)テキストや言葉だけで結構伝えていたなぁと痛感しました。つまり、イメージの理解がおそらく合っていないということです。伝え方もあるかとは思いますが、根本的に各々が見ている視界や視点(DevとBizなども含めて)が異なるため、捉え方・理解の仕方がバラバラになっていると感じました。共通のイメージや認識を持てるよう、図や表現でイメージを伝え、見ている世界がより鮮明になるようにしていきたいと思います。

3. ソフトウェアには、業務知識を反映させる

共通言語の認識を合わせた上で、設計してみるといった内容です。今までは、Dev視点でDevにとってわかりやすいER、クラス、命名などにしておりましたが、少なからず業務フローで使用している言葉を使い、共通認識を持って進めていきたいと思います。その前のファーストステップは、ユビキタス言語やコンテキストマップ作成などから計画的に進められると良いかもしれません。

まとめ

これら3点が少しずつ浸透し、形になって行くだけでも、前述した組織の一課題解決に近づいていくのではないかと考えます。

講義を聞いていて、DDD=オブジェクト指向ではないか?と思った節はありましたが、オブジェクト指向との違いは、やはり「ドメインモデルがエンティティに反映されているかどうか」が大きな違いであることを知りました。何度も言いますが、DDDとは、ソフトウェアに業務知識を反映させることが重要です。つまり、「現実 → モデル → ドメインモデル → コード」を実践することで、ソフトウェアの価値を高めることを目指すものなのです。

ただし、全てDDDにすればいいじゃん!というものでもありません。不向きなシステム、不向きな開発サイクル、場合によっては損益分岐点を下回る可能性もあります。つまり、ドメイン駆動設計は、「Development」ではなく、「Design」であることを忘れてはいけないということです。

何をしたいのか、そのために何が必要なのかを考え、どう実現するかの順番が重要です。

あとがき

現状から見る課題のみならず、未来を見据え、何ができるのか、何をしなければならないのかを考え、一つ一つ課題解決に取り組んでいきたいと思います。またDDDは、まだまだ実践できるほどの知識ではないので、引き続き学んでいきたいと思います。

最後に採用情報です。

当社では、まだまだ採用募集中です。ぜひ一緒に成長していきませんか。ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)

参考資料

コードレビュー自動化 Siderのサービス終了に伴い、GitHub Actionsで実行できるreviewdogの調査・導入をしてみた

こんにちは、2022年4月にフォースタートアップスにジョインしたエンジニアの八巻(@hachimaki37)です。主にタレントエージェンシー支援システム(SFA/CRM)のシステム開発を担当しております。

コードレビュー自動化サービス Siderがサービス終了となった背景から、移行先となるサービスを調査し導入をしました。今回のテックブログでは、調査過程で出てきた疑問、そして調査結果、導入方法などを合わせて執筆していきたいと思います。

背景

タレントエージェンシー支援システム(SFA/CRM)では、コードレビュー自動化サービスとしてSiderを利用し​​ておりました。そんなSiderですが、sider.reviewサービス終了のお知らせとして、2022年12月31日に全てのサービスと技術サポートの提供が終了となりました。

sider.reviewサービス終了のお知らせ

このサービス終了を受け、Siderの移行先となる代替案を検討する運びとなりました。なお所属のチームでは、CI/CDはGithub Actionsを使っております。

目的

普段当たり前に行っているコードレビューですが、導入調査を機にコードレビューの目的ってなんだ?とふと疑問に思い、改めて調べてみました。

コードレビューの目的とは

レビューを通して、欠陥の早期発見による手戻りコストの低減を行い、ソフトウェアの品質を高めることです。

つまり、ソフトウェアの品質を高めるためにコードレビューを行っているのです。とは言え、どのような観点でコードレビューを行なえば、ソフトウェアの品質を高めることができるのだろうか。手動でやる場合、これらを一から明確にし定義しなければなりません。なかなかの手間と時間ですね。

コードレビュー自動化の目的とは

目的にいく前に、まず2つの疑問を持ちました。一つ目は、コードレビュー自体を、そもそも自動化するメリットがあるのだろうか?という点です。この疑問は、目的を理解することで、調査早々に解消されることとなりました。

コードレビュー自動化サービスを導入することで、問題点や気をつけるべき点などを自動的に検知し、開発者に報告してくれます。つまり、コードレビューにかかる時間や人的な見落としを削減することができるのです。とは言え、導入・運用コストに見合うのだろうか?という点が二つ目の疑問でした。

コードレビュー自動化には、以下のような目的も兼ね備えております。

  • コードレビューにかかる時間を削減
  • エンジニア個人や開発チームの成長
  • ソフトウェアの品質向上
  • など

つまり、コードレビュー自動化サービスを導入することで、コスト面以外にも様々なメリットがあることを今回の調査を通じて理解することができました。導入しない選択肢はありませんね。

Siderの概要について

引用元:コードレビュー自動化サービスSiderのご紹介 Sider代表取締役社長 浅原明広氏

タレントエージェンシー支援システム(SFA/CRM)の設定解析ツール

解析ツールの概要

  • RuboCop
    • Rubyの静的コード解析ツール。規約を守れていないコードに警告をだすだけではなく、インデントや改行位置の修正、使用NGなメソッドの置換なども自動で行う
  • Brakeman
  • Reek
    • Rubyソースコードを静的に解析することで、「コードの臭い」を検出するツール

※Reek と RuboCop では、検出する問題の種類に違いがあります。

代替案の調査

そこで代替案の観点として、以下を定義しました。

  • 導入コストが低い
  • ドキュメントが豊富
  • 解析結果をPull Requestのレビューコメントに表示できる
  • 既存の解析ツールのサポートがある

1. reviewdog

概要

代替観点としての評価(※あくまで個人的な意見です)

  • 導入コストが低い △
    • 各種linterを設定ファイルに記載する必要があるため
  • ドキュメントが豊富 ○
    • 概要や導入方法などをGoogle検索した結果、多数ヒットするため
  • 解析結果をPull Requestのレビューコメントに表示できる ○
    • 各種linterの実行結果を渡すことで、Pull Requestなどの差分に関して、警告された行をレビューコメントに表示できるため
  • 既存の解析ツールのサポートがある ○
    • タレントエージェンシー支援システム(SFA/CRM)で使用する解析ツールを始め、様々サポートがあるため

料金

Free(OSS

2. HoundCI

概要

代替観点としての評価(※あくまで個人的な意見です)

  • 導入コストが低い ○
    • HoundCIの公式サイトからGithubアカウントでログインし、監視対象リポジトリを設定するだけのため
  • ドキュメントが豊富 △
    • 概要や導入方法などをGoogle検索した結果、数件しかヒットしないため
  • 解析結果をPull Requestのレビューコメントに表示できる ○
    • コーディング規約違反のコードだった場合、警告された行をレビューコメントに表示できるため
  • 既存の解析ツールのサポートがある △寄りの○
    • タレントエージェンシー支援システム(SFA/CRM)で使用する解析ツールはあるものの、reviewdogと比べるとサポート数が少ないため

料金

  • Chihuahua:Up to 50 private reviews $29 month
  • Terrier:Up to 300 private reviews $49 month
  • Labrador:Up to 1,000 private reviews $99 month
  • Husky:Up to 3,000 private reviews $199 month

その他 候補

調査結果

コードレビュー自動化サービスは、これに決めた!

上記観点を鑑みてSiderの代替案は、reviewdogを採用する形にしました。以下、静的解析ツールをreviewdog(Github Actions)を通して、Pull Requestに表示するよう設定を加えました。

  • Rubocop
  • Brakeman
  • Reek
  • Stylelint

最終的なコード

.github/workflows/reviewdog.yml

name: run Review Dog
on: pull_request
jobs:
  rubocop:
    name: RuboCop
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v3

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.1.1

      - name: RuboCop
        uses: reviewdog/action-rubocop@v1
        with:
          rubocop_version: gemfile
          rubocop_extensions: rubocop-rails:gemfile rubocop-rspec:gemfile
          github_token: ${{ secrets.github_token }}
          reporter: github-pr-review
          fail_on_error: true

  brakeman:
    name: Brakeman
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v3

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.1.1

      - name: Brakeman
        uses: reviewdog/action-brakeman@v2
        with:
          brakeman_version: 5.2.0
          reporter: github-pr-review
          fail_on_error: true

  reek:
    name: Reek
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v3

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.1.1

      - name: Reek
        uses: reviewdog/action-reek@v1
        with:
          reek_version: gemfile
          reporter: github-pr-review
          fail_on_error: true

  stylelint:
    name: StyleLint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16

      - run: yarn install
      - name: StyleLint
        uses: reviewdog/action-stylelint@v1
        with:
          reporter: github-pr-review
          stylelint_input: '**/*.scss'
          fail_on_error: true

設定がうまく行けば、下記のようにGitHub Actionsから各種解析ツールが実行されるようになります。

Successfulの場合

Failingの場合

調査過程の紹介

RuboCop

調査対象として第一に行った解析ツールは、RuboCopです。自動コードレビューの結果をGitHubのコメントに出力するよう設定を加えました。

公式ドキュメント:https://github.com/reviewdog/action-rubocop

公式ドキュメントを参考に、.github/workflows/rubocop.ymlに定義します。あるメソッドを’return if -> if else’に修正したところ、GitHub Actionsから自動レビューコメントが付くようになりました。

その後、残りの解析ツールを公式ドキュメントを参考に設定しました。

Brakeman

ドキュメント:https://github.com/reviewdog/action-brakeman

Reek

ドキュメント:https://github.com/reviewdog/action-reek

Stylelint

ドキュメント:https://github.com/reviewdog/action-stylelint

まとめ

Siderの代替案として、reviewdogを採用しました。reviewdog導入前の印象は、他のツールに比べて、GUIでの設定ができないため、心理的に「大変そう...」でした。ただ、実際にドキュメントを読みながら設定してみると、思いのほか簡単に設定することができます。

実際一番の悩みどころは、Siderの代わりとなる静的解析ツールは何がベターなのかという点です。今回、reviewdog以外のツールを導入しておらずですが、reviewdogは気軽に試せるので、一旦導入してみて運用しながら改善していく方針でも良いのではないかと思いました。

あとがき

最後に採用情報です。

当社では、まだまだ採用募集中です。ぜひ一緒に成長していきませんか。ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)

参考資料

僕たちがBlue/Greenデプロイメントに失敗した理由

始めまして、2022年11月にフォースタートアップ株式会社にSREとして入社した表(@Retomo2214)と申します。 現在は社内向けプロダクト「タレントエージェンシー支援システム(SFA/CRM)」のシステム開発、運用を担当しております。

初めてのブログ執筆のため、拙い文章になっていると思いますが、生温かい目で見守っていただければ幸いです。

はじめに

2022年にRails7へのメジャーバージョンアップ対応およびWebpackerからViteへの移行対応を行っておりました。変更ファイル総数が約1500ファイルとなる大規模リリースをBlue/Greenデプロイメントを実施して失敗した話をつらつらと書いていきたいと思います。 本記事はアンチパターンとして皆様がリリースする際の確認材料の一つとなれば幸いです

本記事をお読みいただく上でのインプットとして技術スタックをご紹介します。

いままでのリリース方法

小規模かつ可逆的な変更を頻繁に行うことを重視しておりますので、インプレースデプロイ(In-Place Deployment)という手法を採用しておりました。 インプレースデプロイとは稼働中サーバに対して直接アプリケーションを配置、再起動するという一般的なリリース手段です。しかし今回は、インプレースデプロイではなくBlue/Greenデプロイメントを採用しました。

▼インプレースリリースのイメージ図

リリース方法変更の理由

下記より今回のリリースは”小規模かつ可逆的な変更を頻繁に行う”という考えから逸脱しており、インプレースデプロイを行った場合のユーザ影響が非常に大きいと考えました。

【今回のリリースの特徴と課題】

  • 更新ファイル数が約1500ファイル

    • 不具合発生時に依存関係の洗い出しや修正に時間がかかる。
  • CI/CD実行に約1時間かかっていた

    • ロールバックする際にも同様の時間が発生するため、もし障害が発生した場合、ユーザが利用できない時間が長期化する

【インプレースリリースを採用した場合に発生しうる課題】

  • 深夜作業必須

    • 稼働中のサーバに対してデプロイを行うため、ダウンタイムが発生し業務影響発生する
  • リリース⇨失敗⇨修正⇨リリースを繰り返す可能性がある。

    • ファイル数が多く依存関係も多々あるため、何度もリリースを失敗する可能性があった。

課題の多いリリースの様に思えましたが、Blue/Greenデプロイメントを採用することでインプレースデプロイの課題点は解決すると考えました。

Blue/Greenデプロイメントとは?

新しいバージョン(Green環境)を本番バージョン(Blue環境)と並行してデプロイし、ELBのルールなどで本番バージョン(Blue環境)と新しいバージョンを(Green環境)へのトラフィックを切り替えリリースを行う手法です。

▼Blue/Greenデプロイメントのイメージ図

メリットとしてはトラフィックで各環境へのアクセスを制御しているため、本番バージョン(Blue環境)をユーザ提供しながら、新バージョン(Green環境)をデプロイとテストを実施することが可能で、ユーザ影響を考えずに、リリースを実施することができます。 逆にデメリットもあり、コスト面とリリース時の複雑性が上がる点です。 良くも悪くもBlueとGreenの2つの環境を用意する必要があるので、リソース利用料が上昇します。 また、リリース自体も環境が増えることでトラフィックの切り替えなどリリース完了までに実施するタスクが多くなります。

私は普段の開発の様に1度の変更が細かいリリースはインプレースリリースを行い、バージョンアップなど変更の粒度が大きいリリースはBlue/Greenデプロイメントが向いていると考えています。

どのように実現したか

まず、今回のリリースは一般的なBlue/Greenデプロイメントとは少し異なります。 一般的なBlue/GreenデプロイメントではGreen環境でテスト完了後にユーザのトラフィックをGreen環境に流しGreen環境を本番環境に昇格して運用します。 ただ今回のリリースではcronなど2重で動くと業務影響がある処理等が含まれていたため、新規でGreen環境用のブランチを切りました。また、Greenブランチをmainブランチに昇格させたのち、Green環境を本番環境に昇格させるのは1回リリースとしては複雑性がかなり高いと考えました。 そこで今回Rails7へのバージョンアップ対応を行ったfeatureブランチをGreenブランチにマージ後、問題なければmainブランチ(Blue環境)にもマージする方法を取りました。

具体的には以下の順序でBlue/Greenデプロイメントのリリースを行いました。

  • 本番用ELBにGreen用のリスナールールとターゲットグループを作成
  • Green用のECSサービスを作成する。
  • mainブランチからGreen用のブランチを切る。
  • Green環境にfeatureブランチ(Railsバージョンアップの作業ブランチ)をマージする。
  • Green環境でテストを実施。
  • mainブランチにfeatureブランチをマージしてBlue環境にリリース

▼全体像のイメージ図

本構成ではDBをBlue環境とGreen環境で共用しているため、開発者によるDB更新処理のテストは実施できません。 代わりに一部のユーザにGreen環境にログインして通常業務を行なってもらい、Green環境での動作確認を実施してもらいました。 本来であればトラフィックを操作して、ユーザを徐々にGreenに流すことで課題は解消されますが、 当サービスではログイン時に認証が行われるため、トラフィック操作時に再度認証する必要があり、 ユーザの通常業務に影響がある可能性があるため一部のユーザにGreen環境にログインして通常業務を行なってもらい、動作確認する方法を採用しました。

リリース当日

Green環境を構築し、数日間ユーザテストとバグ改修を行い、満を持してBlue環境へのリリースを実施しました。 結果は...大量のアラートが発報されました。 すぐにRevertを行いましたが、ダウンタイムが発生してしまい、Blue/Greenデプロイメントは失敗してしまいました。

僕たちがBlue/Greenデプロイメントに失敗した理由

失敗した理由はGreen環境のテスト時に発覚したバグの修正箇所がBlue環境に反映されていないためでした。 Green環境に反映した更新内容がBlueに反映されていなかった理由は、一部の修正がGreen環境から修正用のブランチを切り、マージしていたためです。

▼失敗原因のイメージ図

本来であれば、Green環境で発生したバグはfeatureブランチに反映させ、Green用ブランチにマージすることで、mainブランチ(Blue)にもGreen用ブランチにも同じ修正が入るはずでした。

▼本来想定していたバグフィックス手順イメージ図

結局のところ僕たちがBlue/Greenデプロイメントに失敗した理由は”ヒューマンエラー”です。 気を付けていても人が作業する以上、必ず発生します。 特にリリース時に発生することが多いイメージです。

僕たちがBlue/Greenデプロイメントを成功させるには

今回の失敗を踏まえて対応策は2点あると考えています。 1つ目は「featureブランチからのマージのみGreen環境がデプロイされる様にする」です。 GitHub Actionsをfeatureブランチからのマージのみ発火する様に設定することで、mainブランチとGreen用ブランチのマージ元が同じであることを担保できます。

2つめは「Green用のブランチを切らない」です。 これは一般的なBlue/Greenデプロイに採用されている方法ですが、コミットハッシュを用いてBlueとGreenを管理する方法です。 流れとしては以下の通りです。

  • コミットハッシュをタグにしたイメージをビルド
  • ECRにプッシュ
  • コミットハッシュをタグにしたイメージを利用するBlue用とGreen用のタスク定義を作成
  • コミットハッシュを用いてBlueとGreenそれぞれデプロイする。

▼Gitコミットハッシュを用いたBlue/Greenデプロイメントの図

コミットハッシュを用いてBlue/Greenをデプロイを実施することができれば、コードの中でリリースが完結するので、ヒューマンエラーが発生を抑えられます。 また、Blue環境とGreen環境は同一のブランチに存在するので、Green環境でのテスト完了後そのまま本番環境に昇格させることも容易になります。

まとめ

Blue/Greenデプロイメントは環境を2つ作る必要があるため、どうしても複雑性が高くなってしまいます。複雑性が高いシステムを手動で操作するとヒューマンエラーが発生する確率も上がります。 そのため、下記2点を抑えればBlue/Greenデプロイメントにおける失敗は抑えることができます。 シンプルなBlue/Green構成を構築する。 リリースフローは自動化する。

リリーススピードの観点からは引き続きインプレースリリースを採用する予定ですが、 引き続きBlue/Greenデプロイメント等の様々なリリース手法を精査し、各リリースのタイミングで適した手法を選択していこうと思います。

あとがき

Blue/Greenデプロイメントという手法は以前から認知はしていましたが、 採用は初めてで、実際にやってみると今回の失敗も含めいくつか悩むポイントがありました。 「知っている」と「やったことがある」は雲泥の差ということはよく聞きますがまさにその通りだなと改めて感じたリリースでした。 SREとしてダウンタイムを発生させてしまったのは非常に悔しい結果となりましたが、失敗も資産と切り替えて、皆様に共有したく執筆させていただきました。 今後もどんどんブログを書いていく予定なので、見ていただければ幸いです!!

最後に採用情報です。 当社では、まだまだ採用募集中です。ぜひ一緒に課題解決していきましょう! ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。 採用ページはこちら

参考資料

Figma APIを使用し、svg形式のアイコンを /figma/images 配下にインポートするrake taskを実装してみる

こんにちは、2022年4月にフォースタートアップスにジョインしたエンジニアの八巻(@hachimaki37)です。入社から早半年間が経ちました。引き続き、社内向けプロダクト「タレントエージェンシー支援システム(SFA/CRM)」のシステム開発を担当しております。

はじめに

百聞は一見にしかず!ということで、まずはどんなモノか動画をご覧ください(34秒)

ご視聴ありがとうございました。今回のTech Blogは 、「Figma APIを使用し、svg形式のアイコンを/figma/images配下にインポートするrake taskを実装してみる」です。開発の背景や目的、苦労話などを交えながら執筆していきたいと思います。

事前に様々な記事を拝見させて頂きましたが、アイコンをFigmaで管理する上で、どこも同じような課題感があるんだなと所感を持ちました。

  • アイコンが大量に増えて、管理がめんどくさい
  • 毎度更新されたアイコンをsvgに書き出すのが手間
  • わざわざFigmaを見に行くのがめんどくさい
  • デザイナーがfigma上でアイコンを編集していたことが開発者に伝わらずに、アウトプットが予想と違うものになった
  • アイコンの更新漏れが発生した
  • などなど・・

開発の背景

所属チームでは、2022年7月に新しくUI/UXデザイナーの方を迎え(2022年4月からUI/UXデザイナーが不在)、タレントエージェンシー支援システム(SFA/CRM)のDesign System構築が本格的にキックオフされました。

以下、デザイナーの@Minmi303より得た内容です。

Design System構築に至った経緯

  • 既存のDesign Systemが使われていない(使いづらい)状態だった
  • サービスの詳細、機能、画面全てを網羅している人がいない(網羅するのが難しいほどの画面数)
  • デザインのテイストが2〜3種類ほど混在していた (例:アイコンがデザイナーが作ったものとFont Awesomeが混在していた)
  • 機能追加や削除など、開発のペースが早い
  • デザイナーの効率化を図る仕組みが必要だった(デザイナーが1人のため、作業時間の短縮を図りたい)
  • エンジニアサイドの仕組み化も必要だった(同じパーツでもコードがまとまっていない)
  • などなど・・

Design System構築前に抱えていたUI/UX周りの課題

  • UXを検討するフローが抜けていた
  • デザインレビューのフローがなかった(デザインを担保できない)
  • デザインのトンマナが整っていない
  • 多くの画面がUIを深く考慮されておらず、機能だけがある形の仕様になっていた
  • などなど・・

上記のような背景からDesign System構築が進むにつれ、Colors, Text Rule, Iconsなどが刷新され、開発者用にFixデータがFigmaに格納されるようになりました。

開発の目的

そこで課題に上がったのが、アイコンの格納方法です。

▼議論されたイメージ図

当初、「Figma -> Notion -> タレントエージェンシー支援システム(SFA/CRM)」という格納フローを検討しておりましたが、デザイナー/エンジニア間、双方に課題感を持っておりました。

  • デザイナー: svg書き出しの手間、更新されたアイコンの適応状況 など
  • エンジニア: svgの配置、適用ディレクトリの検討、アイコンの更新漏れ など

結果、Notion管理を不要とし、Figma APIを活用する方針となりました。よって、「Figma -> タレントエージェンシー支援システム(SFA/CRM)」という格納フロー構築を目指し、開発スタートに至ります。

Figma APIとは?

What can I do with the Figma API?

The Figma API supports read access and interactions with Figma files. This gives you the ability to view and extract any objects or layers, and their properties, so you can render them as images outside of Figma. You can then present your designs, connect them to other applications, or use them to expand on your vision. Future versions of the API will unlock even greater functionality around Files.

How does it work?

The Figma API is based on the REST structure. We support authentication via access tokens and OAuth2. Requests are made via HTTP endpoints with clear functions and appropriate response codes. Endpoints allow you to request files, images, file versions, users, comments, team projects and project files.

引用元 公式ドキュメント:https://www.figma.com/developers/api

端的に言うと、Figma API を使用することで、Figma上の様々なデータ取得が可能になります。こちらを用いて、格納フロー構築を行いました。

やったこと

概要

rakeコマンドを実行することで、Figmaからsvg形式のアイコンを/figma/imagesディレクトリにインポートする

大まかな実装イメージ

  • rake taskに以下処理を実装
    • Figma API コールの定義
    • svgダウンロードURLを取得する
    • svgをダウンロードする
    • 生成したファイルにsvgを書き込む

コードの紹介

※アイコンは以下ページから取得することとします

そしてfigma.rakeはこのようになりました

namespace :figma do
  desc 'import svg icons of Figma API'
  task :import_icons do
    # Figma API コールの定義
    file_key = Settings.FIGMA_FILE_KEY
    x_figma_token = Rails.application.secrets.x_figma_token
    figma_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/files/#{file_key}"
    header = { 'X-Figma-Token' => x_figma_token }

    # svgダウンロードURLを取得
    body = get(figma_url, header)
    body['document']['children'].each do |children|
      next unless children['name'] == 'New Icons'

      children['children'][0]['children'].each do |child|
        id   = child['id']
        name = child['name'].gsub('/', '-')
        next unless name.include?('icon-')

        # svgをダウンロード
        svg_download_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/images/#{file_key}?ids=#{id}&format=svg"
        child_body = get(svg_download_url, header)
        child_res = res(child_body['images'][id], header)

        # 生成したファイルにsvgを書き込む
        file = "app/src/javascripts/spa/assets/figma/images/#{name}.svg"
        File.open(file, 'wb') do |f|
          f.write(child_res.body)
        rescue SystemCallError => e
          puts "class: [#{e.class}] message: [#{e.message}]"
        rescue IOError => e
          puts "class: [#{e.class}] message: [#{e.message}]"
        end
      end
    end
  end

  private

  def get(url, header)
    res = res(url, header)
    JSON.parse(res.body)
  end

  def res(url, header)
    url = URI.parse(url)
    Net::HTTP.get_response(url, header)
  end
end

ソースコードの説明を加えていきたいと思います。

Figma API コールの定義

file_key = Settings.FIGMA_FILE_KEY # ※1
x_figma_token = Rails.application.secrets.x_figma_token # ※2
figma_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/files/#{file_key}" # ※3
header = { 'X-Figma-Token' => x_figma_token } # ※4

Figma APIを使用するにあたり、「File Key」と「Access Token」が必要になります。

svgダウンロードURLを取得

body = get(figma_url, header)
body['document']['children'].each do |children|
next unless children['name'] == 'New Icons'

    children['children'][0]['children'].each do |child|
       id   = child['id']
       name = child['name'].gsub('/', '-')

Figma APIをコールすると、

{"document"=>
  {"id"=>"0:0",
   "name"=>"Document",
   "type"=>"DOCUMENT",
   "children"=>
    [{"id"=>"0:1",
      "name"=>"Page 1",
      "type"=>"CANVAS",
      "children"=>
       ・
  ・
  ・
    }]
  }
}

のようなJSON形式で指定したファイルの値が返ってきます。今回Figmaから「New Icons」というページ(上の画像を参照)からアイコンを取得したいため、'next unless'で'New Icons'の値を取得、eachで返ってきた値を変数idとnameに格納します。今回、アイコン名に階層('icon/icon-hoge'のような)があったため、gsubメソッドで置換した値を変数nameに格納する形にしました。

svgをダウンロードする

svg_download_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/images/#{file_key}?ids=#{id}&format=svg" # ※1
child_body = get(svg_download_url, header)
child_res = res(child_body['images'][id], header)

※1 本来は、'https://api.figma.com/v1/images/#{file_key}?ids=#{id}&format=svg"'のようなHTTP Endpointになります

HTTP Endpointには、一つ前で格納したそれぞれのidを、そしてsvg形式で値を取得したい場合、’ format=svg ’を末尾に加え、APIコールをします。すると、以下のような値がレスポンスとして返ってきます。

=> "<svg width=\"108\" height=\"62\" viewBox=\"0 0 108 62\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M53.9998 61.5632C52.0642 61.5632 50.1289 60.8241 48.6532 59.3491L2.21553 12.911C-0.73851 9.95695 -0.73851 5.16748 2.21553 2.21464C5.16838 -0.738212 9.95689 -0.738212 12.9112 2.21464L53.9998 43.3057L95.0887 2.21607C98.0427 -0.736777 102.831 -0.736777 105.783 2.21607C108.739 5.16892 108.739 9.95839 105.783 12.9124L59.3464 59.3506C57.87 60.8258 55.9347 61.5632 53.9998 61.5632Z\" fill=\"black\"/>\n</svg>\n"

これで、svg形式のアイコンを取得することができました。そして、この値を変数に格納し、次のステップへいきます。

生成したファイルにsvgを書き込む

file = "app/src/javascripts/spa/assets/figma/images/#{name}.svg"
File.open(file, 'wb') do |f|
  f.write(child_res.body)
rescue SystemCallError => e
  puts "class: [#{e.class}] message: [#{e.message}]"
rescue IOError => e
  puts "class: [#{e.class}] message: [#{e.message}]"
end

最後に、一つ前で取得したsvg形式のアイコンをファイルに書き込みます。1行目(file = xx)には、変数nameを加えたファイルを定義し、書き込みにはFileクラスのopenメソッドを使用しました。あとは、取得したsvg形式のアイコンをwriteメソッドを使用して、指定したファイルに書き込めば完了です。

はまった点

チケット完了のゴールは定まっていたものの、そもそも実装イメージが皆無だった

  • Figma APIとはなんぞや...
  • どのようなTODOで進めて行けば良いのだろうか..
  • Figma自体さほど触っておらず、理解が浅い...

そんな状況からスタートしました。やったことは、開発着手前に「Figma APIの公式ドキュメントを一読する」、「現時点で思いつくTODOを洗い出す」ことから始めました。今までの経験から、プログラミングで右も左もわからなくなるケースは、大体何をすべきか理解できていないことが多かったため、着手前に思いつくTODOを洗い出し、都度TODOを追加していくようにしました。一例ですが、「Figmaにテストデザインを作成し、そのデザインをFigma API をコールして取得する」ことをTODOに含め実施したことで、「Figma APIの理解が進んだこと」、「やるべきことが都度明確になっていったこと」で、実装イメージが膨らみ、進めることができました。

svg形式のアイコンを取得したかったが、HTTP Endpointを叩くとレスポンスの値がnilになる

以下のようなHTTP Endpointをコールすることで、svgに置換された値がレスポンスされるはずでしたが、なぜかnilになっていました。

'https://api.figma.com/v1/images/#{file_key}?ids=#{id}&format=svg

pry(main)> child_res.body
=> nil

調査を進めていくと、’ ids=#{id} ’に格納した id の値が誤りであることがわかりました。APIのレスポンス値である’ components id ‘を格納していたことがnilの原因であったため、各アイコンに紐づく id に変更することで解消されました。

工夫したこと

今後の運用を考慮したこと(なるべくシンプルに保つ)

運用についてデザイナーと議論し、以下のことを決めました。

  • アイコンの命名規則
    • 基本的に「icon-」の形式とする
  • アイコンのツリー構造
    • 基本的にノード同士は親子関係にはせず、並列とする
  • アートボードの運用
    • 基本的に1アートボードにアイコンを集約する。もしアートボードを追加する際は、エンジニアに一報を入れる。(複数のアートボードからアイコンを取得する場合、データ構造を見直す必要の可能性があるため)
  • exportされるページへの注意喚起
    • 後任者などが見ても理解できるようFigmaのページ上に注意喚起を追記する

ソースコードの汎用性や可読性を考慮したこと

  • 汎用的に使用できるよう、Figma APIコールの定義をsettingsにまとめました。また、X_FIGMA_TOKENは作業者で異なるキーを運用するため、「gitの管理対象ファイルにして、間違えてコミットしてしまう可能性」を考慮し、.gitignoreにあるファイルで管理するようにしました

  • 処理のメソッド化、直感的に理解が進むよう必要最低限のコメントを追記、またソースコードを処理ごとにまとめ実装しました

改善余地があること

Access Tokenの運用方法

APIコールに使用するAccess Tokenに関して、チームで1つのTokenを運用するか、もしくは個人個人のTokenで運用するか、別途議論の余地がありました。所属チームの現時点での運用は、個人個人での運用とし、必要に応じて議論する形にしました。

image optimizerの検討

画像圧縮についてです。理想は、APIコールからsvgをダウンロードした際、プログラム上で画像圧縮が実行できればベストでしたが、優先度の兼ね合いから今回のチケットではスコープ外とし、対応可否を別途検討する形にしました。

Github Actionsでの定期実行

手動でコマンドを叩くこと自体が課題になるかもしれません。こちらも今回の対象チケットには含めなかったため、定期実行をScheduleに組込み、自動実行できる仕組み構築が今後必要になってくるかもしれません。

まとめ

実装から無事リリースに至り、これから運用へとプロセスを踏んでいきます。運用しながら課題が新たに発生するかもしれませんが、ひとまず元々感じていた双方の課題を解消できる開発になったのではないかと思います。

あとがき

私自身、今回初めてFigma APIに触れましたが、思いのほか手軽に実装することができました。というか、初めてでもわかりやすい仕様だったということでしょう。デザイナー、エンジニア双方の課題感を知り、実装を進められたこと、非常に楽しくワクワクしながら開発できました。

最後に採用情報です。 当社では、まだまだ採用募集中です。ぜひ一緒に成長していきませんか。 ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)

参考資料

スプリントレトロスペクティブ本来の目的とは?初めてファシリをやって体感した「難しさ」と「学び」

こんにちは、2022年4月にフォースタートアップスにジョインしたエンジニアの八巻(@hachimaki37)です。主に社内向けプロダクト「タレントエージェンシー支援システム(SFA/CRM)」のシステム開発を担当しております。

今回は、スクラムのイベントの一つである「スプリントレトロスペクティブ(以下、レトロスペクティブ)」について書いていきたいと思います。

早速ですが、レトロスペクティブをやっていて、こんなこと思ったことありませんか?

  • 最近なんとなくやってるなー
  • テーマを出したいけど、これで大丈夫かな..(迷う..)
  • TRY実行してるけど、何が改善されたんだ?
  • チームで議論すべきテーマってこれでいいの?
  • ふりかえりやだなぁ..

みたいな・・

ぜひこの機会に1つ考えて頂きたいことがございます。 それは、「あなたは、なぜレトロスペクティブ(ふりかえり)を行っていますか?

そんな「なぜ」を解消する内容にしてみました。

レトロスペクティブとは?

スクラムチームは、個人、相互作用、プロセス、ツール、完成の定義に関して、今回のスプリントがどのように進んだかを検査する。多くの場合、検査する要素は作業領域によって異なる。 スクラムチームを迷わせた仮説があれば特定し、その真因を探求する。スクラムチームは、スプリント中に何がうまくいったか、どのような問題が発生したか、そしてそれらの問題がどの ように解決されたか(または解決されなかったか)について話し合う。

スクラムチームは、自分たちの効果を改善するために最も役立つ変更を特定する。最も影響の大きな改善は、できるだけ早く対処する。次のスプリントのスプリントバックログに追加することもできる。 スプリントレトロスペクティブをもってスプリントは終了する。スプリントが1か月の場合、スプリントレトロスペクティブは最大 3 時間である。スプリントの期間が短ければ、スプリントレトロスペクティブの時間も短くすることが多い。

引用元:スクラムガイド

これを読んで考えたことは、「スプリント中に何がうまくいったか、どのような問題が発生したか、そしてそれらの問題がどのように解決されたか(または解決されなかったか)について話し合う」ことはあくまで手段であり、「自分たちの効果を改善するために最も役立つ変更を特定する」ことが最も重要だということです。

後述しておりますが、当初私が考えていた重要なことは、「スプリントであった出来事を整理し、チームの課題を改善すること」でした。ここに大きなギャップがありました。

所属チームのふりかえり手法と決め方について

所属チームでは、2週間のスプリント最終日にレトロスペクティブを開催し、経験年数に関わらずサイコロを使いランダムにファシリテーターを決めています。ファシリテーターに決まった方は、自身でふりかえり手法や段取りを考え実施します。

当社のレトロスペクティブの歴史としては、特定のファシリテーターがおり、基本的にKPTにてふりかえりを行っていたみたいですが、日々改善される中で今の形へと変わっていきました。

実施したレトロスペクティブの紹介

デイリーハッスル

デイリーハッスルは、「日常の慢性的なわずらわしい出来事を解消していくふりかえり手法」です。ストレスマネジメントの権威であるLazarusとFolkmanが1980年代に提唱した概念で、ストレス心理学におけるストレッサーの区分モデルのひとつです。

ラクティスとしてのデイリーハッスルは、この記事を参考に実施させていただきました。

引用元:ふりかえり手法のおもちゃばこ(前編)

  • 実施結果

Time Line

Time Lineは、「事実と感情の両方を合わせて書き出していき、全員で共有するふりかえり手法」です。書き出した事実を時系列に並び替え共有することで、チームの持つ情報を整理し、カイゼンのためのアイデアを出しやすくします。

  • 実施結果

工夫したこと

チーム状況に合った手法を選択

  • デイリーハッスルをやった時期は、新しいメンバーが加わったこともあり、身の回りの問題に焦点を当てられるふりかえり手法を選択しました。また合わせてメンバーの価値観(どんな場面でデイリーハッスルを感じるのか)を共有できるチャンスでもあったため、目的に加えて実施しました。
  • Time Lineを行った週は、イベントが多いスプリントであったため、日々やったことをチームで整理できるふりかえり手法が良いかと考え、Time Lineを実施しました。

Notionを活用

チーム内に出社組・リモート組がいたため、場所に囚われないようNotionを活用しました。ホワイトボードを活用する機会が多かったですが、ふりかえり手法によってはNotionも使用可能だと成功体験を得ることができました。

付箋の色分け

Time Lineを行う際に、付箋の色を分けたことで「事実」と「感情」の判別がしやすくなり、スムーズな進行が可能になりました。

体感した「難しさ」と「学び」

議論すべきテーマの選択

ふりかえりの議論となったテーマが、チームに対する共有事項であったり、影響範囲がチーム外の事柄、個々人によりすぎたテーマとなってしまいました。良し悪しというよりは、何のためにレトロスペクティブをやるべきなのか?を考えるきっかけになりました。

本当にそのTRYでProblemを解消出来るのか

つまり、TRYの精度です。TRYのアイディアが出されるも「本当にProblemを解消出来るTRYになっているのか?」を考えすぎてしまいました。正直やってみないことには何もスタートしません。そのため「決めたTRYを実行し、改善を繰り返す」という目線に変え、TRYを決めていきました。

レトロスペクティブの目的とは?

私が考えていたレトロスペクティブの意義

  • チーム課題を改善すること?
  • チームで洗い出したProblemを改善すること?
  • スプリントであったことを整理すること?
  • スプリントをふりかえること?
  • チームで決めたTRYを実行すること?
  • ...

私が考えていたレトロスペクティブの目的(意義)は、「スプリントであった出来事を整理し、チームの課題を改善すること」だと考えていました。しかし、スクラムガイドを再読した際に、レトロスペクティブ本来の目的を理解していなかったことに気づきました。

レトロスペクティブ本来の目的

スプリントレトロスペクティブの目的は、「品質と効果を高める方法を計画すること」である。

引用元:スクラムガイド

つまり、「チームがもっと効率を高めることができるTRYを計画できたか」です。

重要なファクター

スクラムガイドでは、以下2点が重要であると述べられているように思います。

  1. スプリント中に何がうまくいったか、どのような問題が発生したか、そしてそれらの問題がどのように解決されたか(または解決されなかったか)について話し合う。 ◯所属チームは、現状できている
  2. 自分たちの効果を改善するために最も役立つ変更を特定する。 ✖️所属チームは、現状できていない

これらを通して思ったことは、目的を意識することが大切だということです。「目的を理解していない = いつまでたってもゴールにたどり着くことはできない」。つまり、品質と効果を高められるTRYを計画できるにはほど遠いという事実を知ったことが、今回感じた最大の学びでした。

今回得た学びを実践した結果をふりかえる

今回の学びを通じて、個人的に決めた3つのTRYを実行してみました(3に関しては、時間の都合でできず..次回のTryとして実施していきたいと思います)

  1. レトロスペクティブの目的をチームへ周知する
  2. 自分たちの効果を改善するために最も役立つであろう変更を議論のテーマとする
  3. 品質と効果を高められるTRYを計画できたかをふかりえり後にチームに確認する

まず1に関しては、事前にタイムスケジュールを組み、レトロスペクティブ開始前に実施しました。やったことは、今回得た知見をチームへアウトプットするといった内容です。

共有自体はさほど難しいことではありませんが、やはり最大の課題は2の「最も役立つであろう変更を議論のテーマとする」ことでした。一人3票、議論したいテーマに各々投票する形式にしましたが、普段よりも投票に時間がかかった印象でした。チームとして、何を改善(議論のテーマと)すれば、「チーム効率がもっと高まるのか」を考える必要があるため、いつもよりも俯瞰的に物事をみる必要があったかと思います。

ご参考までに今回議論となったテーマです。

  • 朝会の運用をもう少し改善できないか?
  • 開発途中で仕様変わってない?

改善活動1回目のふりかえりの場でしたので、新たな思考のきっかけを今回作れたのではないかと思っております。引き続き継続していきます。

まとめ

Q1. レトロスペクティブ(ふりかえり)の目的は?

A.「品質と効果を高める方法を計画する」ことです。

Q2. レトロスペクティブ(ふりかえり)では、どんな議論をすべきか?

A.「スプリント中に何がうまくいったか、どのような問題が発生したか、そしてそれらの問題がどのように解決されたか(または解決されなかったか)について話し合うこと」です。

Q3. その理由はなぜか??

A.「自分たちの効果を改善するために最も役立つ変更を特定する」ためです。つまり「チームがもっと効率を高めることができるTRYを計画する」ために必要なファクターなのです。

あとがき

当社開発チームは、現在2つのチームで開発を行っています。今回ブログを執筆する前に共通のLT会でレトロスペクティブに関する発表をさせて頂きました。

所属チームは、2週間のスプリント最終日にふりかえりを行い、改善活動を行なっています。

良いスプリントだったとふりかえることもあれば、改善多数とふりかえることもあります。さまざまな変数があるかとは思いますが、効率を高められた!良いTRYが実行できた!素敵な価値をユーザへ提供できた!というようなポジティブな状態をチームで作っていけるよう、日々の改善活動を継続して行なっていきたいと思います。

最後に採用情報です。

当社では、まだまだ採用募集中です。ぜひご一緒に良い組織・チームを作っていきませんか。 ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)

スモールチームのインフラ担当・SREとして入社し取り組んだことと失敗したこと

こんにちは。フォースタートアップス株式会社でインフラ・SREを担当している吉田です。昨年入社しました。 今回は、インフラ・SRE担当の一人目として入社してからこれまでの約1年間で取り組んできたこと、失敗してきたことや課題をお話します。

入社時の弊社の状況

入社したのは約1年前。当時はオーナーや開発メンバーなど含め社員3〜5人のチームが2つあり、それぞれ別のサービスを開発していました。 社員の他にも、チーム内にはパートナーやインターンの方々もいて、約十名が曜日で入れ替わりながら参加いただいていました。

組織の状況は以下でした。

  • 各チーム各サービスとも、開発も進み機能が徐々に増えてきた
  • より開発や運用の安定性をより高めていきたい
  • サービスレベルの目標設定もしたい

そんな組織の、インフラやSREの担当の一人目としてジョインしました。 入社前は2人チームとお話をもらっていましたが、諸事情で専任の一人目となり、インフラやSREの業務をゼロからスタートすることとなりました。

当時はこの2つのチームを横断して、インフラ関連業務やサービス開発と信頼性の向上に取り組むこととなりました。

入社してから取り組んだこと

直近の活動方針を決める

同じ業務をするメンバーもおらず、右も左もわからない状態からのスタートだったので、

  • まずは、メンバーとサービスを理解しよう
  • そこから、今困っていることを探して改善/整備しよう

と、ざっくり活動方針をたてて、以下の直近の活動内容をつくりロードマップをひきはじめました。

  • サービスの現状理解する
    • アラームやインシデントの対応と棚卸し
    • サービス目標と追いかける指標の策定
  • 整備と改善

上の内容に取り組む中で、良かった点と失敗した点がありました。

アラームやインシデントの対応と棚卸し

着任してから、まずは、

  • サービスの開発や運用の状況や構成を知ろう
  • 安定性を高めるためにはどこからなにをはじめるか、ポイントを探ろう
  • 今サービスの安定をおびやかす事象にどのようなものがあるのか知ろう

という考えのもと、

  • サービスを知るには、まずは日々のアラームや障害の対応にガンガン参加することだ!
  • チームが今、何を大事にして何を追っているのか、まずはつかむぞ!

という思いで動き出しました。

...というのは半分建前で、実はその裏では「早く馴染みたい、早く役に立ちたい」という焦りや不安がとても大きかったのです。

というのも、各チームは人数が少ないこともあってか、

  • チームの方針に則りながらも各メンバーが自律的に積極的に動いている
  • 誰かがアサインしなくても開発Issueがどんどん上から解決されていく
  • また、サービスの問題もチームで話し合い分析し、Issue化して協力して取り組むことができている

という、自律しながらも協力して取り組む文化のある、統率のとれたチームでした。

このチームとともに活動をしていくためには、

  • 自分も早くIssueを取れるようにならないといけない
  • 早く各チームのメンバーに、一員として認められたい
  • この無力感を脱したい

という焦りを感じていました。

そのためには「四の五の言わず、まずは現状把握と対応だ!🔥」という(勝手な)思いがありました。

失敗:入社直後はなかなか役に立てず空回り

まずは各チーム各サービスのアラームやインシデント対応に参加をはじめます。が、しかし早速つまづきます。

  • 発生するアラーム、発生する事象が、日々とても多い
  • 発生している事象が、どこの何が起因なのかがつかめない
  • サービスを構成するサービスが複数存在し、データストアの相互参照もあり
  • 対応を優先するべきアラームなのか、対応が不要なアラームなのかの判断がつかず、勝手に右往左往してしまう

既存のメンバーはそのアラームの過去の発生頻度や様子から、発生箇所の特定やおおよその原因を掴んで進んでいきます。

私はサービスの理解が浅いことも有り、問題の概要がつかめずに対応の初動まで時間がかかってしまいます。その間にまた別のアラームや事象が発生します。

  • アラームがなる、とりあえず確認する
  • これどこのなんのサービス?影響範囲は?前後の事象と関係がある?
  • これはなに?これは見るの?これは見なくていいの?
  • と思っていたら、別のアラームが...

入社して数日は素早い対応がなかなかできず、悔しい日々が続きました。

気づきと学び:まずは状況観察とヒアリングが大切

日々発生する事象の対応をしながら、少しずつですがサービスの概要や業務の流れを掴み始めました。

同時に、xxxの情報があったらもっと詳しい調査ができるのではないか、原因不明の事象もxxxのログから追えるのではないか、と、一歩引いた目線でインシデントを振り返る時間もできました。

最前線で対応することはもちろんですが、この小さいチームで「SREとして、対応者や門番の役割をさらに超えてチームと伴走し、よりサービスの信頼性をあげるには今何が必要なのだろうか」を考え始めました。

開発メンバーと同様に対応をはじめるだけでなく、別の観点やアプローチをつくることでチームとしてより早く問題解決ができるのではと感じ始めます。

対応人数が少ないからこそ、発生する事象が多いからこそ「まずは状況を観察し判断すること」、インシデントの対応にも言わばトリアージのようなものが一定は必要であると感じました。

実際複数のアラームやインシデントが同時に発生することは少なくなく、その場ですぐに優先度をつけることを求められる場面がありました。

複数の対応の優先度付けや対応箇所の判断ができるようになるためにも、今この初期のタイミングで観察や事象の整理をしなければいけないと気づきました。

このことから、インシデントの検知アラームや障害の対応と並行して、事象や通知を棚卸しをしながら、優先度付けや原因特定がしやすくなるように、以下に取り組みはじめました。

  • アラームや通知は精査をして不要なものは流さないこと
  • アラームや通知情報を意味ある情報にすること
  • 本当の関係者に通知すること(サービスも事象も多いが人数は少ないため、担当に注力できるようノイズを極力減らす)
  • 属人化しないよう、誰が見ても同じように状況を認識して動き出せるようになること

例えば、特定の指標の増減や稼働率低下などのSLOを満たしていない事象を通知する場合においても、少し極端な例ですが、


稼働率が下がってきた!やばい!
< 応答速度があがってきた!やばい!

と通知をしても、メンバーは


< 具体的に何が起きてるの?
< サービスのどこがおかしいの?

となり、対応の初動に時間がかかってしまうことが多いです。

なので、そうではなく、例えばですが、


<xx時にxxのエラー発生でユーザにエラー画面が見えている
<xx日からxxのAPI応答時間が延びている

と通知をすれば、メンバーは、


< xxのAPIだから、影響範囲はxxだね
< xx日ということは,もしかしてxxのリリースがきっかけかも...

と、起きている事象の理解や当てをつけることがしやすくなります。

必要な情報を取捨選択して、プロダクトやアプリケーションの詳細な出来事に落とし込み、事象解決につながる情報にして届けることが大切なのだと、当たり前のことではありますが再認識しました。

とすれば、アラームや通知内容は、目指すサービスレベルをおびやかす(SLO違反)警告となるものにするべきです。

今思えば、後述するサービスレベルや追いかける指標(SLI/SLO)の精査に、より早く着手するべきだったと反省しています。

またSLO違反の際に各自がどのように動き出すべきなのか、関係者同士である程度でも目的や方針などの認識をそろえておくともっとスムーズに動けるはずです。

ロードマップや目下の開発を進めることとのバランスもスコープに入れると、メンバーは動きやすくなりますし、チームの心理的安全性の向上にもつながるのではと思います。

良かったこと:現場だから生まれるコミュニケーション

前述のような失敗はありましたが、一方で入社直後で不慣れなことが多い中でも、アラームやインシデントの対応がきっかけでうまく進み始めたことも多くありました。

入社3日目と4日目に続けてインシデントが起きました。具体的には、Elasticsearchの高負荷やノードのダウンによる遅延やエラー応答が発生したのですが、これがきっかけで入社後すぐにチームのメンバーと仕様の深い話をし始めるきっかけができました。

自分 「ちなみにxxxってどうなってるんですか?」
メンバ「これはxxxとxxxでつかっているんだけど、負荷かかってるんだよねー」
自分 「そしたらxxxの部分を見てみますね、ちなみに普段どうやって確認されていますか?」

また、その会話をきっかけに、

「xxxの実行時間と頻度って変更できるものですか?」
「xxxのパラメータで改善できそうです、サービス影響を相談させてください。」
「xxxの作業するので、夜間作業に立ち会っていただけませんか?」

というように、一緒に作業をするきっかけも作ることができました。

対応者として前線に立つことによって、参加できる会話や見える課題もあるのだなと感じた瞬間でした。

サービスの目標と追いかける指標の策定

入社のタイミングで「各サービスの目標(稼働目標など)を定義したい」という課題があり、こちらにも着手しました。

私が入社前からエラーの発生数や応答時間のメトリクスが作成されており、ダッシュボードでそれらを見るという文化もありました。

この文化にも助けられました。どんな指標を大事にしていたか、何に問題意識をもっていたか、がなんとなく掴めたからです。

一方でメンバーからは、各指標の値はわかるが、サービスがどんな状態なのかわからない、何か良い方法はないかと相談を受けました。

そこで、サービスと構成を調べながら、

  • サービスが正常に動いているというのはどんな状態なのか
  • サービスが正常ではない状態は、どのように観察/検知できるか

を関係者と話し合い、取り急ぎ第一弾として、その条件からシステムの稼働率(≒ SLI)を定義しました。

定義後はチームで稼働率を追っていけるように、稼働率と関連項目を確認するためのダッシュボードを作成し、情報共有とモニタリングをはじめました。

ダッシュボードの表やメトリクスは、指標の数値や推移に加えて、現在目標ライン(≒ SLO)を上回っているのか下回っているのかがぱっと見てわかるように、状態に応じて表示色を変えたりメトリクス内にしきい値を描画したりするなど、まずはわかりやすさを重要視しました。

サービスが正常に動作し価値を届けられているのはどのような状態か、また現在それが目指すレベルにあるのか、チームで共通認識を持ちはじめられたことが良かったです。一方で、見える化したことによる副作用もありました。

良かったこと:サービスの現状理解が進み、稼働に対する意識が高くなった

まずは見える化したことで、以下の気づきや変化がありました。

  • 想像以上に未応答数や不安定な時間があることがわかった
  • エラーや未応答に意識が向くようになった

既存のダッシュボードでも、その瞬間の応答状況は確認できていましたが、メトリクスで推移を見ることで過去分も確認できるようになりました。

その結果、想像以上に応答率が落ちていたり遅延していたりするタイミングがあることをチームで共有できました。

今では、リリース時の確認や、朝会などでの定期的な確認をするようになり、稼働(サービス提供)への意識もより強くなりました。

失敗したこと:SLO違反との向き合い方の意識合わせ不足

稼働への意識が強くなったことは良かったのですが、現在のサービスとチームでは初めてのSLI・SLOの制定であったこともあり、どうやって運用していくかまでは気がまわっていませんでした。目標値を下回った(SLO違反が発生した)場合のチームの動き方まで、チームと話ができていなかったことが最初の反省点です。

現在のチームが、サービスに対してとても思いが強く責任感のあるチームだったこともあり、そんなチームにとっては「SLOに違反している ≒ 価値が提供できていないユーザがいる」があることはインパクトのあることでした。

そのため、設定当初はメトリクスの上下や一件のエラー発生の通知すべてに反応していました。

一方でサービスの性質上、ユーザーも利用環境も非常に種類が多く攻撃や不正なアクセスも少なくありません。それもあって、見える化をした直後は、認識していなかったエラーも観察されると同時に検知機構も過敏に反応し不要な通知をするなど、粗い部分もありました。

当時はエラーとして検知しても、本当に内部で発生したエラーである場合もあれば、存在しないもしくは許可されていない箇所への不正アクセスである場合もありました。そうすると、対応に疲弊してしまったりアラームや通知がオオカミ少年のように認識されてしまったりすることもありました。

どのエラーが本当に対応しなければいけないもので、どのエラーが誤検知や対応不要なものであるかを、導入の初期段階ではまだ精査しきれてはいませんでした。

エラー検知やSLOの基準となる指標の上下に必要以上に一喜一憂することなく、状況を把握して冷静に対応するためにも、SLOや指標の通知やメトリクスをどのように業務に組み込んでいくのかの認識をあわせることがまずは必要でした。

特にSLOを設定したばかりでチームに馴染むまでは、SLOは何を目的として設定しているのか、満たせている場合はどんな状態で、満たしていない場合はどんな状態で何をしなければいけないのか、を言語化して共通認識を持てるように共有することが必要でした。チームに根付かせるプロセスをもっとしっかり踏まなければいけなかったと反省しています。

失敗したこと:設定後の運用にまで手が回っていないこと

また、指標や目標の見直しまで手が回っていないことも反省点のひとつです。

新しい機能の提供や新しい形式でのコンテンツ配信が増えれば、それぞれに応じて指標や目標ラインも変わるはずです。例えば、APIとHTMLや動画と音声では、それぞれユーザに応答が届くまでの目標時間も異なるはずです。また新しい指標が必要になることもあるはずです。

また、稼働率が低下した要因を追えるようなモニタリングも当時は考慮できていませんでした。例えば内部エラーの検知や発生時のリクエストの詳細情報などです。

SLIやSLOの運用や見える化が、サービスの変化や成長に追いついていませんでした。

特に初回に設定した目標は、しばらくは定期的に確認と検討をする機会を作るべきでした。アップデートを踏まえたサービスの提供価値とそれを満たす状態を見直す機会を設けていきます。

ボトルネックの解消

サービスの状況を見える化した後は、運用やシステムのボトルネックを見つけて解消することに着手しました。

アラームやインシデントの棚卸しと稼働状況の見える化で、性能低下のタイミングや業務コストが高い箇所がつかめたので、その原因やボトルネック箇所の特定の解消、そのためのデータやログの収集や通知に取り組みました。

具体的には以下に取り組みました。

たとえば、特定の時間帯の応答時間の増加をダッシュボードで確認したため調査をはじめたところ、ログやトレース情報からデータ登録時にエラーと数回のリトライの発生を確認しました。参照もそれに引っ張られる形で影響をうけ、アプリケーションの応答速度に影響がでていました。

詳しい内容と原因特定のため、アラームやトレース情報などのモニタリング項目を追加し事象の調査しました。

調査の結果、弊社ではElasticsearchを利用しているのですが、そこでリクエスト過多のエラーが発生していたため、データ更新量やスレッドプールの状態を観察して処理を特定し、データ登録方法やクラスターの設定を見直し、応答速度の増加を解消することができました。

結果的にアラームの見直しやモニタリングが、ボトルネックの発見と解消につながったのは良かったです。

脆弱性の対策

以下のような脆弱性の対策にも着手しました。

  1. 脆弱性スキャンのツールやサービスの利用
  2. 外部の診断会社による診断の実施
  3. 定期的に脆弱性情報を収集/確認
  4. クラウドやネットワーク設定の確認

1と2については、外部の診断会社や各種サービスを利用して、サーバ/コンテナやアプリケーションに脆弱性がないかスキャンをはじめています。

3については、開発言語やライブラリに関連する脆弱性を、手動でピックアップし調査しています。が、後述しますが、まだうまく運用を回せていません。

4については、クラウドやネットワークに誤った設定(意図しないポート公開やアクセス設定など)がされていないかをチェックしています。

都度確認するのは難しいため、事前にポリシーをつくり、そのポリシーに則っているかをスキャンするようにしています。 このポリシーの作成と運用についてはAWS Config とAWS CDKを用いた手法で取り組んでいます。以下の書籍にまとめていますので、よろしければご覧ください。 https://techbookfest.org/product/5762421668970496?productVariantID=6028076402081792

難しかったこと:活動の必要性や必要なコストの妥当感について理解を得ること

特に2については、診断にかかるコストは安いものではありません。金銭はもちろんですが、環境やデータ準備や診断会社からのヒアリングへの対応もあるため、SREだけでなく開発チームにも負荷はかかります。

1や3のスキャンや情報収集も運用コストがかかります。ユーザに提供価値として直接目に見えて届くものではなく、効果の実感につながるまでに時間も機会も必要です。

意味のある脆弱性対策として機能させるには、継続的な活動と活動を行うための一定のリソースが必要になります。

また、せっかく調査して得た脆弱性情報も、サービスに与える影響に落とし込めないと、情報が流れても誰にも有益な情報としては伝わりません。

「私たち(のサービス)にどう関係あるの?」になるのは当然です。


< xxxでディレクトリトラバーサルの危険あり!
< xxxでオーバーフローの脆弱性が見つかった!


< うちのサービスに関係あるの?
< どのサービスがどう攻撃されるの?

人も工数も少ない小さなチームの中で、継続的な活動にコストを使わせてもらうためには、チームに活動の意義やコストの妥当性が伝わっていることが必要なのだと感じました。

良かったこと:脆弱性に関する書籍の輪読会をおこなったこと

弊社では毎週書籍の輪読会をおこなっているのですが、上の課題に取り組む時期に、輪読会で書籍「体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践」を読む機会をつくることができました。

  • どんな脆弱性や攻撃手段があるのか
  • アプリケーションにどのような影響があるのか
  • 対してどのような対策が必要なのか

輪読会でこれらについて学び議論し意見交換をすることができました。

脆弱性の概要や種類を体系的に学ぶことはもちろんですが、自分たちが開発しているサービスではどんな危険性がありどんな対策が必要なのかを、議論しながら深堀りができました。

この輪読会のおかげで、必要性の認識や対策意識について共通認識を作り始めることができました。とてもありがたかったです。

ちなみに、弊社ではビブリオバトルで輪読会の書籍を決めます。ちなみにこの安全なWebアプリケーションの作り方を推薦したのは私です。https://tech.forstartups.com/entry/2022/03/11/110557

課題:スキャン結果の確認と対応のコスト確保

その後、対策の活動をはじめたのですが、スキャン結果をいかしきれず重要度が高くないものは対応が先送りになるケースも少なくありません。

特に実際に4つの脆弱性対策の施策をはじめた直後は、重要度が低いものも含めて大量に検出してしまい、どこから着手すべきかわからなくなるほどでした。

また、3については、開発言語やライブラリに関連する脆弱性を手動でピックアップし調査していました。ですが、どうしても時間がかかってしまうため、今は大きい脆弱性が見つかった場合に個別で調査するにとどまっています。

1と4で、CI/CDへの組み込みや定期スキャンを実施し、2で外部の専門の会社に診断を依頼してカバーしてもらいつつ、緊急性の高いものや詳細な調査が必要なものを3で調査する、という運用にとどまっています。

スキャンをする方法はそろってきたので、スキャン結果取得から対応までに時間が空いてしまわないように、情報の整理と対応を効率的にできるように改善していきます。

利用SaaSの管理

インフラのリソースとあわせて、SaaSクラウドサービスのアカウントやコストの管理もはじめました。

  • メンバー一覧作成(メール・Githubアカウント)
  • アカウントの発行と削除
  • アカウント発行/削除方法のドキュメント化
  • 毎月の請求業務
  • プランやアカウント変更の見積もりと稟議申請
  • 新しいSaaSを利用するときの確認や申請、法務(リーガルチェック)連携など

開発で多くのSaaSを利用している一方で、各チームにインターンやパートナーの皆さんがいる関係で、不定期にメンバーの増減があります。

アカウント発行が必要なタイミングで、シートが増えてプランも変わり決済が必要となるため、社内の稟議申請も必要になります。

利用しているSaaSをメンバーの情報と一緒に管理をしはじめ、SaaSの利用が必要になったタイミングで必要な権限のアカウントを発行し、費用の増減も合わせて記録・申請する形式で運用をはじめました。

良かったこと:アカウントとコストの管理が一緒にできることの効率性

前述のとおり、メンバーの増減が不定期にあるため、アカウント発行とプランや料金変更も不定期にあります。

都度、チームメンバーが稟議を作成して申請業務をしていては、業務のスイッチングコストも増えてしまいます。担当者が曖昧な状態で、申請や管理業務が漏れていたこともありました。

金額と利用状況をあわせて管理することで、チームで見たときにコミュニケーションや申請業務のコストも抑えられると考えました。

また、不在時に急ぎ必要な場合も、用意した手順書でアカウント発行/削除できる運用にしておくことで、管理人数が少なくとも対応ができるようにしておけば

メンバーが少ない組織でも、各チームを横断して契約や管理ができるようにしておくことで、少しは手間やコストを抑えながら管理できているかなと思っています。

課題:アカウント発行のタイミング

一方で、SaaSのアカウントをいつ発行するか、運用に迷っていることもあります。

  • チーム参画時にアカウント一括で発行
  • 必要になったタイミングで都度発行

「チーム参画時にアカウントを一括で発行」すれば、アカウント数や費用も見積もりがしやすく、アカウントの登録や削除も利用しているSaaSすべてについて実施するため、ある程度決まった手順で実施ができるため、管理の手間が省けるはずです。アカウント管理のサービスも使いやすいはずです。

一方で業務内容やポジションによって、利用するSaaSに違いもあることから、アカウント発行してもメンバーによっては利用が少ないSaaSもあります。

また、アクティブでないアカウントが気づかずに不正アクセスされるケースもゼロではないと思います。

職種やロールによって、アカウント発行のパターンを作ることも検討しましたが、パターン化することも簡単ではなく、結局必要になったタイミングで追加することになりそうで断念しました。

「必要になったタイミングで都度発行」することで、不正アクセスや使用が少ないアカウント分のコストも抑えることはできますが、アカウント発行の作業は都度不定期に発生しますし、誰に何を発行したか記録しておかないと、全サービスを確認する必要があるためアカウント削除作業の手間が増えます。

現在は、メンバーが多くないことも有り、SaaSの費用削減をねらって「必要になったタイミングで都度発行」の方法をとっています。

誰にどのSaaSアカウント発行をしたか記録する目的も兼ねて、SlackにSaaSアカウント管理の専用のチャンネルを作成して運用しています。

これで、発行依頼や削除依頼にある程度機械的にルールを設けることで手間を減らすことを試みていますが、アカウント周りについてはまだまだ改善しなければいけないことが多いです。

ポストモーテム

これは私がはじめたものではなく前任の方が導入してくれていたのですが、月に一度ポストモーテムの場を設けてチームをまたいでインシデントの振り返りをしています。こちらも入社後に引き継いで継続しています。

専用のリポジトリを用意しており、インシデントの対応が完了した後にリポジトリにPRを作成しフォーマットにそって記録する運用としています。フォーマットはSREの書籍の付録にあるものとほぼ同じです。

月に一度、PRを元に各チームから発表してもらう形式をとっています。

失敗したこと:発表するメンバーの負荷の考慮不足

発表する側の負荷の高さが課題のひとつです。

現在のフローは、以下のようになっており、担当者が自主的に情報をまとめレポートを作る必要があります。

  • インシデントの対応完了
  • 担当者がポストモーテムのリポジトリにPR作成
  • 当時の関係者でPRレビュー
  • 月に1度の発表の場でPRのレポートを発表
  • PRをマージして完了

ようやく対応が終わってからのインシデントの発表を億劫に感じるメンバーもいたため、日々の業務や運用フローにからめるなどて、もう少し発表者の負担を軽減できないかと運用を見直している最中です。

また、コストはかかるものの、ポストモーテムの場で共有することで自チームにも他チームにも学びがありサービスの向上につながると認識してもらうために、もっと体験を良くできる方法を模索しています。

失敗したこと:議論しやすい場を作り切れていないこと

参加人数が増えたせいか、以前よりも議論や考察の会話の量が減ってきています。

以前は、対応内容について意見の交換があったり、改善のアイデアをライトに言い合ったりする場だったのですが、人数が増えて一人あたりの発言の機会が少なくなったせいか、以前よりも議論の時間が少なくなりました。

また、以前は報告されるインシデントも大小様々で、中にはインシデント発生まで至っていなくとも、ヒヤリハットや運用の学びの出来事が発表されることもありました。

おそらく、議論のネタやアイデアを持ち込むことや、議論そのものに参加することで得られる良い体験や学びの実感が薄くなってきてしまっているのだと思います。

今は全チーム一緒になって開催しているため、一人ひとりの発言の機会を増やすために、報告後の議論をいくつかのグループにわけての実施や、チーム単位での実施も検討しました。ですが、自チームの事象についてそもそも当事者であるためすでに議論がなされていることもあり、他チームとの共有や意見交換も意義の一つであると思い、開催の変更には至っていません。

現在もポストモーテム自体は開催していますが、参加してくれるメンバーが良い体験と感じてもらえるように、議論を促したり質問が気軽にできたりするような場をつくれるように日々やり方や進行を模索しています。

まとめ

課題や失敗は色々ありましたが、総じて「情報を整理して、共通の文脈にあわせた上でチームに共有して、共通の認識をつくっていくこと」をもっと大事にして、「活動’の意義や価値を実感してもらうこと」を増やすべきであっと反省しています。言語化して伝えるだけではなく体験として実感してもらうことが一番伝わると思っています。

チームの設立時からジョインしていたり、同じ立場やロールで動くメンバーがいる場合には、共通言語や共通認識の基礎があるため、それらにずいぶん助けられていたのだと感じました。

複数のスモールチームに参画し、新しいことを始めて運用に取り入れて継続的に活動していくことは、土台づくりからとなるため、時間もかかりとても難しいです。

ですが、各チームの各メンバーがオーナーシップをもってサービス開発に取り組んでおり、良い活動となりそうなことについては積極的に取り入れる文化があることに大きく助けられました。そのおかげで新参者の私が新しいポジションでも活動を続けられています。

「良い活動となりそう」と感じてもらった上で、実際にサービスの価値や信頼性をあげられるように、今後もチームと一緒に議論しながら活動していこうと思います。

これから

現在は、IaCやCI/CDの改善、ログ収集と分析に取り組んでいます。

また今年になって、インフラおよびSRE業務にメンバーが一人増えました。

できることも増え、速度もあげられているのでとてもうれしいです。また何より、各チームと関わりはあったものの、チームとしては一人だったので、楽しさやモチベーションもあがりました。

ですが、まだまだ課題も取り組みたいこともたくさんあります。

フォースタートアップスでは一緒に取り組んでくれる方を募集しています。まだまだ未熟な部分は多いですが、興味を持っていただけましたらぜひご連絡ください。