まっしろけっけ

めもてきなやーつ

Ruby version up 作業手順メモ

はじめに

最近副業で Ruby version up 業を行いました。(2.7 -> 3.0 -> 3.1)
本業の方でも古い Ruby を使っており EOL が近づいたら version up を行うという方針だったのですが、
EOL が来たらあげるんじゃなくてこういうルールで上げていきましょうという日本語を書いて共有したらやっていくことになった。

それで、僕はもう version up 業ができるので本業の方でも僕が作業してもあまり得られるものがないので、
それならば別の誰かに作業して貰う方がその人の成長に寄与するのでは?と思ったので他の誰かに任せることにした。(予想以上に手を上げてくれる人がいた)

その為、これまで僕がどのように Ruby の version up 業を行なってきたか?をメモって公開することにした。(特に社内限定である意味がないので)

ちなみに Ruby は 1.9 から使い始めて 3.1 までの version の version up は全て対応したことがあります。

前提条件

前提として以下の状態であること。

  • Rails を使用した web application の Ruby の version をあげる際の話
    • gem の場合も大きくは外れないと思いますが
  • spec が十分に存在する
    • 足りない分は version up の機会に追加して良いのであればこの限りではない
  • Rails 以外の gem は適度に update されている
    • update が 1 年以上止まっているとかの場合は先に gem の version up を全て終わらせてからの方が作業が進みやすいと思われます
    • 結果的に Rails の upgrade を行う際も効率よく進められる

実際の作業

0. 方針を決める

事前に決めておいた方がいいのは deprecation warning をどうするか?という点です。
リリース前に対応するのか?リリース後に対応するのか?次の version にあげる際に対応するのか?あたりになると思います。
チームで話し合って決めるのが良いと思います。

僕はリリース前に対応するのが好みです。

1. Release Note を見る

例えば Ruby 3.0 の場合は下記のページ
www.ruby-lang.org

release note を眺めてざっとどんな変更があったか?を脳内に記憶しておくと脳内に index が作られて後々エラーなどが発生した場合に原因の追求が楽になります。

2. 開発環境の Ruby を version up 後の version にする

docker 上で動かしている場合は Dockerfile 等に書いてある Ruby の version を変えたり
local の PC 上で動かしている場合は rbenv install x.x.x や .ruby-version を変えて rbenv install したりしてあげようとしている version の Ruby を install します。

3. 必要な gem を入れる

Rails の application は Gemfile で管理されているはずなので bundle install を行うだけです。
この段階で gem によっては RUBY_VERSION を見ており install が失敗する可能性があります。その場合は該当の gem に対応 PR を送っておきましょう。

4. rails が起動するか確認

まずは bundle exec rails c で rails console を立ち上げてみてまともに起動するか?というのをみます。(別に s で server 立ち上げでも問題ないです)
ここで立ち上がらない場合は表示されている error log から原因を探っていきます。原因に関しては個々の application や環境で異なってくるので対応方法は個別に判断しましょう

5. CI で動いている Ruby version を変える

続いて CI を通すために CI 上で使っている Ruby の version を変更します。

GitHub Actions なら .github/workflows/*.yml
CircleCi なら .circleci/config.yml

上記あたりで使用している Ruby の image の version を変更しましょう。

僕が担当していたサービスでは CI 上で Ruby 2.7 を使っているつもりだったのに実は CI 上は Ruby 2.6 が動いていたという問題があったために下記のように Ruby version をチェックする spec を書いていたりしました。

describe RUBY_VERSION do
  it { expect(RUBY_VERSION).to eq('2.7.0') }
end

6. CI を全て通す

5 の対応で CI 上で version up 後の Ruby が動くようになるはずなのであとは落ちる test をひたすら修正していきます。

7. 不安であれば動作確認も行う

CI を全て通すことに成功したのちまだちゃんと動くか?というのが不安であれば開発環境にて動作確認を行うのも良いでしょう。

8. version up 前の Ruby でも問題ない変更の場合は事前にリリースを行う

例えば Ruby 3.0 では keyword arguments の非互換性な変更があります。
しかしこれらの変更に関しては Ruby 2.7 の段階で対応していても何ら問題ない場合がほとんどになるので事前に対応して Ruby2.7 の段階でリリースを済ませておくと良いでしょう。

メリットとしては下記のようなものがあります。

  • version up 時の Pull Request の差分を少なくできる
    • レビュワーの負担軽減
  • version up 前に問題ないコードだという安心を得られる
  • version up 後にもし問題が起きた場合の原因特定が容易になる

9. version up の Pull Request を作成する

8 を行なったことで Pull Request の差分も最小のものになりレビューもスムーズに終わるはずです。

10. 本番などの環境の Ruby を管理している file なども変更する

これは個々の環境よりますが、開発環境と同じ Dockerfile を使って動いている場合は変更しなくても 9 のものをリリースすれば勝手に変更になります。
本番などが別の Dockerfile なりプロビジョニングツールなりで管理されていた場合はそちらに変更の PR を送るなどを行なってください。

11. リリース

staging 環境などがある場合に動作確認をしたいなどのパターンはあると思いますのでチームの方針によって行なって貰いますが、残りはリリースするだけになります。

エラーが発生しやすいパターン

上記のように作業進めていく中でいくつかエラーが発生しやすいパターンがあるので軽く触れていきます

default gem になるなどして使用していた gem がなくなる

Ruby 本体に含まれなくなっただけで、実際には無くなっておらず Gemfile に追記して gem を引き続き使用することが可能です

非互換性な変更によるエラー

先で紹介した keyword arguments の変更など非互換性な変更によりエラーが発生することが稀に存在します。
Release Note を読んだりすればどのような変更を行えばエラーを解消できるか?は書いてあります。

gem が対応していない

Ruby 3.0 のように非互換性の変更がある場合は特に多いのですが、使っている gem が使いたい version の Ruby にまだ対応していないことがあります。
その場合はその gem に対応するための Pull Request を送ってみましょう。
既にその gem の更新が止まってしまっており Pull Request が取り込まれなさそうな場合は、代替えの gem を探す/fork したものをメンテする/独自に実装する/軽微なものなら monkey patch を当てる等の方法を取る必要があります。どの方針を選ぶかはチームで話し合って決めるのが良いでしょう。

最後に

Ruby の version をあげる際に自分が行なっている作業を雑にまとめました。
忘れてたことがあった場合は後で追記しておきます。

次回は Rails 編を書きます。

Rails に contribute する実績を解除した記念

はじめに

Rails を使い始めて 8 ~ 9 年くらい?経つのだけれど、なかなか機会が無く contribute 出来ていなかったがやっと出来たので記念に残しておく

内容について

github.com

こちらの PR なのですが、このバグを踏んだ経緯から説明します。

副業でお仕事をしているのですが、dalli の version up を行った時のことです。
dalli は v3.0 から大幅な変更が加えられており、独自で実装していた DalliStore という session store から Rails 標準の MemCacheStore 経由で Dalli::Client が呼ばれるという方式に変わりました。

PR をみてもらえればわかりますが、このバグは Dalli を v2.x から v3.x にあげたときに DaliStore(dalli v2.x) で memcached に格納されたデータを MemCacheStore 経由で read_multi を使って取得するときに発生するものになります。副業の方では read_multi で指定している key が 2 つしかなかったことから fetch で取得する方法に変更してこのバグを回避したのでお仕事としてはこれで終わりだったのですが、どうしてもこの解決方法に納得できず昼くらいに解決したけど夜までモヤモヤが残っていました。

モヤモヤの原因は Rails と Dalli どちらを修正するべきなのか?の判断が難しいものだったからで判断しかねていたので MemCacheStore の過去の PR で Dalli 関連のものってないのかな?と思い調べました。

それで辿り着いたのがこの PR でこの PR が取り込まれているということは Rails でもある程度 Dalli を利用した際の挙動について担保しておくということなのでこの PR を参考に test を書いて、該当箇所を修正したという感じ

最後に

最近は OSS 活動バグがあったら PR 出す程度しかしてないけれど Rails へ contribute できて嬉しかったので記念カキコ

最終出社

はじめに

本日 12/28 日が 6 年 6 ヶ月勤めた GMO ペパボの最終出社になります。とかいてますがこの記事を書いてるのは 27 日で公開したのは 28 日 0 時なのでまだ最終出社してません。1,2 月は有給消化になり 3 月から次の会社で働きます。

ペパボでの 6 年半の話

やったことなど

EC の API を開発するところから始まり 2 ヶ月ほどで minne に異動してきた後は、カオスだった API 開発に秩序をもたらしたり Solr から Elasticsearch への移行を行なった。その後検索周りの改善などもしていたら Apple Pay のローンチパートナーになったのでそれ関連の開発をしていた。

Apple Pay が落ち着いたあたりで EM に任命され EM 業をやりながらマイクロサービス化や k8s への移行、その他にもインフラとバックエンドを軸に様々な改善をしつつフロント周りも少し開発したりなど楽しく開発してきた。(正直多すぎて覚えていない...)
minne のメインとなるリポジトリの追加行数より削除行数の方が 1.4 倍くらい多かったのは割と気に入っている。

やってきたことはこのブログや SlideShare / Speaker Deck にも書いてあるので詳細はそちらを

おもしろイベントなど

(URL は載せませんが)企画でお見合いをしたりジョージアの web CM に出たりと色々なおもしろ体験をさせて貰った。
また、上でも書いたが Apple Pay のローンチパートナーになったおかげ(?)でちょっと特殊な状況で開発したり Apple の製品ページで minne の画面を継続的に使って貰えたりなどもあった。
普通に生きてたら(?)体験できないことだったので良かった。

評価など

(今は知りませんが)前職が新卒超優遇評価だったので、フラット且つオープンな評価制度で良かった。
入社する時は(誰にも言ってなかったが) 1 年でシニアエンジニアになれなかったら辞めるという強い気持ちで入社し、ありがたいことに評価していただき 1 年でシニアエンジニアになることができた。
その後も半年後には CTL となり、等級と名称が変わり SEL になるなどちゃんと評価されてきたと思う。数年前に評価制度が変わってからは(相対評価ではないものの)密かに SEL の中で一番いい評価を取り続けるんや!という気持ちを持ちながら仕事をしていたし実際に辞めるまでそういう評価を貰った。(バリューを出せばちゃんと評価してくれるので同じ職位の中で一番バリューを出すというような意味で)

今年 5 月くらいからはシニア・プリンシパルとして活動していた。

お金など

評価の項目にも書いた通り、割と順調に職位を上げてきたのと数年前に 200 万アップの件もあり入社当初から比べると倍以上になった。

総合的に

総合的な話をすると、ペパボで働いて正解だったなと思う。優秀で尊敬できるエンジニアも多いし、某ライバルのエンジニアとは同い年ということもあってか切磋琢磨してこれたのではないかなと思う。また既にやめてしまっているが同じ部署で CTL を一緒にやっていたエンジニアと一緒に仕事をしていた時期が僕は一番楽しかった + 色々な面で学びが多かったと感じれる時だった。

居るだけで成長できる環境をうたっていますが(今もうたっているっけ?)正確には居るだけで(皆に平等にチャンスがあり、それを掴みにいき成果を出せれば)成長できる環境があるっていうのが正解なのかな?ってのが個人的な気持ち。なのでちゃんとチャンスで手を挙げる勇気と覚悟を持て、それを放り出さずに周りの力も使いながら成果を出す自信がある人にはオススメかなって思います。


それじゃ、なんで辞めるんや?という話ですが特に面白い話でもないので直接聞いてくれれば答えます。

さいごに

はじめにも書きましたが、3 月から次の会社になります。どこか?は働き出して少ししたら書くと思います(が給料は 2 割くらい上がりますとだけ書いておきます)

1,2 月は有休消化で暇なので最後に例のリストを貼っておきます。

Amazon.co.jp

ISUCON に初参加

はじめに

ISUCON is 何?という人は下記を参照

isucon.net

過去も何度か出たい〜と思っていたのだけれど、だいたい予定と被ってて無理じゃん...となること数回...初参加を果たしたのであった。
チームは社内で募集している人々がいたのでその人々と参加した。

反省など

初参加だったので事前準備などはせずに臨んで見るか〜と思って過去問などはやらずに挑んでみた。

初めて触るアプリケーションってなんであんなに楽しいのか...楽しすぎて夢中になってしまい本来の目的を忘れてしまっていた感も若干ある...

反省としては

  1. レギュレーションは最初にちゃんと読んで理解すること
  2. チームメンバーの役割を明確にする

あたりが大きいやつかな〜という感じ
ベンチマーカーの仕様とかを理解する以前にあーでもないこーでもないみたいなの初めてしまったのは本当に失敗...朝だったので日本語から逃げたかったというのもあるけど... 15 ~ 16 時くらいに理解し出してなるほど〜(完全には理解していない)となり、イジっていったらスコアが伸び出したという感じでここからや〜というところで終わってしまったのであった...

最後に

次回も出たいし、今回の解説とかはまだみてないので見ないでもう一回自分でアレコレやってみてから解説などを見てアレコレしたい。

楽しかったので出てよかった。

GMO アワード 2021 にノミネートされてた話

はじめに

だいぶ時間が経ってしまったのですが GMO アワード 2021 というものにノミネートされていました。
is 何?という人は下記を参照

hr.pepabo.com

感想など

前職でも似たようなものは存在していて自分とは無縁と思っていたのでこういうのにノミネートされるのは素直に嬉しいという感想

また、ペパボからの過去のノミネート者を見ても @hsbt@matsumotory など尊敬するエンジニアが名を連ねているので選んでいただけたのはありがたいな〜という感想とまだ自分は先にあげた二人と肩を並べるほどのエンジニアではないので頑張っていかんとな〜と思うなどした。

最後に

という記念カキコでした

DeployGate のアカウントを PullRequest ベースで管理したい

はじめに

DeployGate is
deploygate.com

会社で使ってたりするとアカウント管理とかが大変ですよね。退職したらその人のアカウント消したり、気づかないうちにカオスになってしまうことも....

そんなカオスな事を出来るだけ回避したいので Github などで PullRequest ベースでアカウントの追加/削除ができるとちょっと嬉しい気がします。Enterprise プランであれば SAML が使えるのでそれでも良いと思います。

yaml でユーザを管理する

使う gem はこちら

gem install deploygate-client

これは元同僚の Android エンジニアがまだ一緒に働いてた時に「API とかあるならコードで管理するようにしたら?」と言ったら作ってたやつ。結局コード管理するまで至らずに退職しちゃったんですけどね...

まずは既存のアカウント一覧を yaml に吐き出す。

require 'deploygate/client'
require 'json'
require 'yaml'

org_name = "管理したい org"

client = Deploygate::Client.new(token: ENV['DEPLOYGATE_TOKEN']) # 環境変数の DEPLOYGATE_TOKEN にトークンを設定する
response = client.organization_members(org_name: org_name)
members = JSON.parse(response.body)["members"].map { |val| val["name"] }

YAML.dump({members: members}, File.open('members.yaml', 'w'))

これで members.yaml に既存のアカウントが書き出される

次は yaml に書いてあるアカウントを DeployGate 側に反映される。

require 'deploygate/client'
require 'json'
require 'yaml'

org_name = "管理したい org"
dryrun = ENV['DRY_RUN'] # 環境変数で DRY_RUN を設定するとアカウントの増減はしないで変更予定の出力だけ得られる

client = Deploygate::Client.new(token: ENV['DEPLOYGATE_TOKEN']) # 環境変数の DEPLOYGATE_TOKEN にトークンを設定する

response = client.organization_members(org_name: org_name)
api_members = JSON.parse(response.body)["members"].map { |val| val["name"] }

yaml_members = YAML.load_file('members.yaml')["members"]

add_members = yaml_members - api_members # yaml に書いてあるのに DeployGate に存在しないアカウントは追加したいアカウント
delete_members = api_members - yaml_members # yaml に書いてなくて DeployGate に存在するアカウントは消したいアカウント

add_members.each do |member|
  puts "add #{member}"
  client.add_organization_member(org_name: org_name, user_name: member) unless dryrun
end

delete_members.each do |member|
  puts "delete #{member}"
  client.delete_organization_member(org_name: org_name, user_name: member) unless dryrun
end

こんな感じでこのコードと members.yamlGithub で管理して Actions などで master に merge されたタイミングで実行するようにすれば PullRequest ベースで管理できるようになります。

エラー処理とかは特に行なっていない(ネットワークエラーとかで更新失敗しても再実行すれば yaml と同じ状況にできるので)

最後に

deploygate-client よくできてる。

Istio を利用して HTTP Request 時に問題があったら retry してもらう

はじめに

Istio is
istio.io

service mesh の一種で色々な事ができるので上記参照(雑)
内部的に envoy を使ってたりする。

今回やる事

特定の pod から別の pod にアクセスする際に稀にネットワーク的な問題で繋がらなかったり、アクセス先の pod が高負荷でたまたま処理に失敗するなどが発生する時がある。

そういう時に pod で動いているアプリケーション側で retry の機構を実装する事も可能なのだけれど、外部との通信で毎回書くのはダルかったりコードが複雑になったりするわけなのでそれを service mesh 層でやってもらおうという作戦。

Retry してもらう

istio の install などは省きます。

設定方法はここにそのまま書いてあるのですが実際に動作確認したいよねとなるので動作確認用のアプリケーションを書いたのがこちら

main という web アプリケーションから sub というアプリケーションに Request を送るだけの簡単なもの

deployment にはまだ istio-proxy 等が追加されていないので apply する際は追加してあげる必要がある

$ istioctl kube-inject -f manifests/deployments.yaml | kubectl apply -f -
$ kubectl apply -f manifests/services.yaml
$ kubectl apply -f manifests/virtual-services.yaml

これで準備終わり。別の pod から curl http://istio-example-app-main-svc:9080/ を打つとこの設定だと普通に Response が受け取れるはず。

sub のアプリは 2s sleep 後に Response を返す実装なので VirtualService の設定の timeout を 2s 以下に変更する。
変更箇所はここ

$ kubectl apply -f manifests/virtual-services.yaml

この状態で curl http://istio-example-app-main-svc:9080/ すると upstream request timeout となり Response が受け取れません。
なぜかというと istio-proxy が VirtualService で設定されている perTryTimeout で設定されている時間で一旦接続を切り attempts の回数分再度同じ Request を行なっているからです。

この様子をログで確認する方法は下記

$ kubectl logs -f main-pod istio-proxy
$ kubectl logs -f sub-pob istio-proxy

これで Response Time によって Retry を設定する方法がわかったので次は HTTP status などによって Retry する設定です。
これはここの retryOn で設定ができます。

どういう設定を書けばいいのかは envoy の docs に書いてあります。

動作確認をしたい場合はこちらを使用して上記と同じ手順を辿れば確認できます。

最後に

今回紹介した機能以外にも istio にはいっぱい機能がある(ありすぎる)ので何かしらの課題を解決する為に導入してみるのも良いかと思います。