for Startups Tech Blog

フォースタ社員のエンジニアたちが思い思いのことを書き綴ります。

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へのアップデートは諦めることにしました。

2023/07/06 追記

unicornはもうメンテナンスされていないgemです。」と書きましたが。現在も開発が続いているようです。 以下のリポジトリは開発中のリポジトリGitHub上にミラーリングされたものになります(これはrubygems.orgのページからはたどり着けないので注意)。

GitHub - defunkt/unicorn: Unofficial Unicorn Mirror.

このことは中村 涼(r7kamura)さんから教えてもらいました。 この場を借りてお礼申し上げます。誠にありがとうございます。

また、unicorn_error.logの内容について、レスポンスヘッダーの値に本来利用すべきではない5などの値が入っていないかどうか確認することも助言頂きました。 重ねてお礼申し上げます。

ただ、上記GitHubリポジトリの最新が本番運用できるものなのかどうか私では判断できませんでした(バージョンタグなどもついていない)。 そのため、件のGitHubリポジトリの最新unicornを使うようにするのか、pumaなどに置き換えるのかはチーム検討中です。

追記終わり。

終わりに

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としてダウンタイムを発生させてしまったのは非常に悔しい結果となりましたが、失敗も資産と切り替えて、皆様に共有したく執筆させていただきました。 今後もどんどんブログを書いていく予定なので、見ていただければ幸いです!!

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

参考資料