はじめに
こんにちは。enza サーバーサイドエンジニアの緑川です。先日、運用しているプロダクトでRubyのバージョンアップを行いました。
Rubyのバージョンアップ対応をするのが初めてだったので、自分の振り返りも含め、改めて今回実際に行った対応をまとめてみました。
この記事がRubyをバージョンアップする際に少しでもお役に立てれば幸いです。
バージョンアップの手順
私たちのチームでは使用している言語やライブラリのバージョンを管理し、優先度を付けながら定期的にバージョンアップを行っています。 Ruby2.5系の公式サポートが今年3月までとなっていため、今回Ruby2.5.8 → 2.6.8へバージョンアップを行いました。 今回のバージョンアップはこのような手順で行っています。- 公式リリースのチェック
- 各種ファイル記載のRubyバージョンを上げる
- gemのバージョンアップ
- CIが通ることを確認
- 性能試験をする
- QAチームの検証
- 本番環境リリース
公式リリースのチェック
今回は2.5.8 → 2.6.8のバージョンアップだったので、その間のバージョンアップでどのような修正が加えられているかの差分を、リリースノートやnewsで確認します。観点としては既存のコードに影響が出るような変更が含まれていないかをチェックします。- サンプルコードでわかる!Ruby 2.6の主な新機能と変更点
各種ファイル記載のRubyバージョンを上げる
Rubyのバージョン管理ファイル等、Rubyのバージョンを指定する記述があるファイルを修正していきます。バージョンアップに伴うエラーなのかどうかを切り分けるために、事前にRSpecが通ることを確認してから始めましょう。Dockerfileを修正
Dockerイメージを作成するための定義ファイルであるDockerfile
にもRubyのバージョン指定の記述があるので、修正します。Alpine Linuxを使用しているのですが、今回こちらのバージョンも上げました。
ARG RUBY_VERSION=2.6.8 ALPINE_VERSION=3.13 ARG BUNDLER_VERSION=1.17.3 ARG BASE_PACKAGES="busybox-suid git openssh shadow tzdata logrotate" FROM ruby:${RUBY_VERSION}-alpine${ALPINE_VERSION} as builder ...Ruby2.6.8と対応するAlpine Linuxのバージョンが3.13 or 3.14だったため、3.14へバージョンを上げました。
今回少しハマったポイントとなるのですが、コンテナのbuild時に下記のようなpermimssionエラーが発生してしまいました。
...原因を調査するとalpine3.14では、Linuxのセキュリティ機能であるseccompを使用する際に誤ったエラーが返されるような不具合があったようでした。そのため今回はバージョンを3.13で指定するようにしました。 参考:
#13 4.467 ERROR: While executing gem ... (Gem::FilePermissionError) #13 4.467 You don't have write permissions for the /usr/local/bundle directory.
...
https://gitlab.alpinelinux.org/alpine/aports/-/issues/12821 https://github.com/opencontainers/runc/issues/2151
CI用Ruby imageをバージョンアップ
私たちのプロダクトではCircleCIを使っており、そこで使っているDockerイメージのRubyバージョンを変更しました。CircleCIから公式のDockerイメージが提供されており、今回そちらを使用しました。.circleci/config.yml
のdoker-imageを書き換えるだけです。
... rails_image: &rails_image image: circleci/ruby:2.6.8-node environment: RAILS_ENV: test DYNAMODB_HOST: "http://127.0.0.1:8000" MYSQL_HOST: "127.0.0.1" REDIS_HOST: "127.0.0.1" ...nodeも使っているプロジェクトなので、node入りの
ruby:2.6.8-node
を使うことにしました。
バージョン管理ファイルを修正
Rubyのバージョン管理ファイルの指定を修正します。 rbenvを使っている場合は.ruby-versiton
、asdfを使っている場合は.tool-version
を変更します。ちなみに、asdfはrubyのようなプログラミング言語だけではなく、kubectlやterraformなどのツールのバージョン管理も行うことができるので便利ですね。
- 2.5.8 + 2.6.8
RSpec、RuboCop実行
各種バージョンアップファイルを修正後、RSpecが問題なく通ることを確認します。RSpec実行時には問題なかったのですが、RuboCop実行時に下記エラーが出ました。
$ bundle exec rubocop静的コード解析ツールのgemであるRuboCopが、Ruby2.6系に対応していないバージョンである旨のエラーが発生したため、今回はRuboCopのバージョンアップも対応しました。
Error: Unknown Ruby version 2.6 found in .ruby-version. Supported -> versions: 2.2, 2.3, 2.4, 2.5
gemアップデート(RuboCop対応)
上記理由でRuboCopのバージョン上げたので、それに依存するgemもアップデートしています。また、RuboCopのバージョンが上がったことに伴いデフォルトのコーディング規約も若干変わったため、該当する記述の修正も行いました。
下記は対応したルールの一部です。
Layout::BlockEndNewline
- Block内のendが独自の行に記載されているか
# bad blah do |i| foo(i) end # good blah do |i| foo(i) end→ 可読性を上げる観点から、こちらのルールを適応させました。 参考:
https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Layout/BlockEndNewline
Naming/RescuedExceptionsVariableName
- レスキューされた例外の変数に期待どおりの名前が付いているかどうか(デフォルトだと”e”)
# bad begin # do something rescue MyException =>; exception # do something end # good begin # do something rescue MyException => e # do something end→ exceptionが使われている箇所があったので、変数名をeに統一しました。 参考:
https://www.rubydoc.info/gems/rubocop/0.75.0/RuboCop/Cop/Naming/RescuedExceptionsVariableName 上記のようなコーディング規約の対応をしていき、無事CIが通ることを確認しました。
性能試験をする
CIが通ることを確認したら、開発環境へデプロイし手動で動作確認します。 問題ないことを確認できたのちに、サービスのパフォーマンスに劣化が発生していないかを確認する性能試験を行いました。私達のプロジェクトではGatlingを使って性能試験を行っており、こちらも大きな性能の劣化がないことが確認できました。 下記はGatlingでの出力結果です。
性能試験後は、検証環境でQAチームにテストを実施してもらい問題ないことを確認してもらいます。
これでRubyのバージョンアップが無事に終了です!おまけ Ruby2.6系で使えるようになった使えそうな機能
(とっくに最新のRubyバージョンを使っているぜ!という方は温かい目で見守って下さい)Ruby2.6から使えるようになった新機能や変更された点は沢山ありますが、使えそうなものを少しだけ抜粋。
to_hでブロックを受け取れるようになった
# キーに大文字、値に小文字を持つ要素のハッシュを生成する # before ["Hoge", "Fufa"].map {|x| [x.upcase, x.downcase] }.to_h #=> {"HOGE"=>"hoge", "FUGA"=>"fuga"} # after ["Hoge", "Fuga"].to_h {|x| [x.upcase, x.downcase] } #=> {"HOGE"=>"hoge", "FUGA"=>"fuga"}ハッシュ生成時に、ブロック内で [key, value] となる配列を返すと、それが要素になるハッシュが生成されます。
merge(merge!)メソッドに複数のハッシュを渡せるようになった
# 値に文字列を持つ要素のハッシュを生成する # before a = { a: "hoge" } b = { b: "fuga" } c = { c: "piyo" } a.merge(b).merge(c) #=> { a: "hoge", b: "fuga", c: "piyo" } # after a = { a: "hoge" } b = { b: "fuga" } c = { c: "piyo" } a.merge(b, c) #=> { a: "hoge", b: "fuga", c: "piyo" }Ruby2.6以前はmerge(merge!)メソッドは1つの引数のみ受け付けていましたがが、Ruby 2.6からは複数のハッシュを渡せるようになりました。
終端を省略できるようになった
# 配列内の要素を取得する # before a = [0, 1, 2] a[1..] #=> syntax error, unexpected ']' a[1...] #=> syntax error, unexpected ']' # after a = [0, 1, 2] a[1..] #=> 1, 2 a[1...] #=> 1, 2Ruby 2.6から範囲(Range)リテラルで、終端を省略して書けるようになりました。Ruby2.6以前では
a[1..-1]
のように書きます。
その他変更はこちらで
- Ruby Documentation
- サンプルコードでわかる!Ruby 2.6の主な新機能と変更点
おわりに
本記事ではRubyのバージョンアップ手順についてまとめてみました。私達のプロダクトは、複数のリポジトリが関係しており、バージョンアップの影響が他リポジトリへ影響することもあるので、バージョンアップの内容を確認しながら慎重に行いました。
最新のRubyバージョンは3.0.2(安定版 2020年9月現在)なので、これからガンガン上げていきたいですね。
Rubyはすんなりいきましたが、Railsのバージョンアップの方が色々と大変だったりするので、次回はRailsのバージョンアップをやってみようと思います。