GitHub Actionsで Ruby を使うための現状と展望(2019/01/05時点)

2020/02/08追記

GitHub Actionsでrubyを使うなら ruby/setup-ruby を使おう - masa寿司の日記 に本記事より後の状況をまとめました。

はじめに

actions/setup-rubyにPR出したのをきっかけに masa-iwasaki/setup-rbenvを作ったりして actions/setup-ruby周りで多少知見が溜まっているので、まとめてみます。

注意事項

  • GitHub Actionsに関する説明や他CIと比較などは省略します。
  • 断りがない限り、すべて現時点での話です。
    • 後半で書きますが、今後一気に状況が変わる可能性があります。
  • ほぼ自分が使っているLinux環境(Ubuntu 18.04)の話だけ書いてます。そして、もちろん私の独断です。
  • タイトルどおりの内容なのですが、もしかするとRuby以外の言語やソフトウェア・ライブラリにも役立つ話かもしれません

対象者

  • これからRubyのプロジェクトをGithub Actionsに移行しようかなと思っている人
  • すでにRubyのプロジェクトをGitHub Actionsに移行したけど actions/setup-ruby でハマっている人
  • Ruby使っていないけど actions/virtual-machines のhosted tool cacheで問題を抱えている人

これから使おうとしてる人向けの手っ取り早い結論

  • GitHub謹製であるactions/setup-ruby は現状ほとんどの場合、期待に見合う動作をしないので見送りましょう
    • が、改善される気配があるので興味ある人はwatchしましょう
  • すでにCI用のdocker imageがあるならdocker image使うほうが導入は早いです
  • docker-composeなどdocker上ではなくVM上で直接動かすことが前提のツールを使いたいのであれば setup-rbenvなどの自前ビルドを使いましょう。

それでは、ここから長い解説です。

Rubyを利用するための選択肢

現状、以下があると考えています。

  • OSが提供するパッケージを使う
  • dockerイメージを使う
  • actions/setup-ruby
  • 自前でビルドする
    • rbenv, rvmを利用する

GitHub Actions で Ruby使いたい場合のパターン

ものすごくざっくり分けるとライブラリ開発とアプリケーション開発に分かれるのではと考えてます。

  • 前者の場合はTravisCIが(さくっと設定できるという意味で)得意とする複数バージョンのRubyでのテスト(以後、ビルドマトリックスと呼ぶ)が求められる。
  • 後者の場合は多くの場合 .ruby-version で指定されているRubyのバージョンをピンポイントで使いたいことが多い。

上記パターンに対する選択肢別の使える度合い

  • x : 使えない
  • △: 状況により使えないとはいえないが、基本使えない
  • ○: 使える

の3択でそれぞれの選択肢を比較した結果を以下にまとめてみます )

選択肢 マトリックス利用 .ruby-version利用
OSパッケージ x
docker
tool cache
自前ビルド

(2020/01/06 22:08 修正: dockerでのビルドマトリックス構築についてサンプルを頂いたので、docker利用時のビルドマトリックス選択について評価を△から○へ変更し、該当箇所の説明を編集しました)。

まずOSパッケージという選択肢ですが、通常単一バージョンのRubyしか使えないのでビルドマトリックスを構築したい場合は無理です。仮に複数バージョンあったとしても、毎年新バージョンが出るRubyに追随することはないでしょうし。.ruby-version利用の場合は使いたいバージョンがぴったり合えば使えなくはないので△にしましたが、OSが提供するバージョンと使いたいバージョンがぴったり合って、かつアプリケーション側が .ruby-versionを全く変えないという状況は限定的と思われるので、ほぼ x と思ってもらってもいいでしょう。

dockerについては、ビルドマトリックス、.ruby-versionともに利用可能なイメージを準備できれば利用が可能です。 jobs.<job_id>.container (詳細はGitHub Actionsのワークフロー構文 - GitHub ヘルプを参照)を利用することで以下の事例のようにビルドマトリックスを構築できるようです。

ただし、 id:sue445 の以下の記事によると matrix.image の評価が期待通りに動作しないケースもあるようですので、使い方次第では注意が必要かもしれません。

sue445.hatenablog.com

次の setup-ruby ですが、matrix で簡単にビルドマトリックスを指定できるようになっているものの、利用可能なバージョンが中途半端に最新に追随していないため、2.4-2.6でパッチバージョンを気にせずテストできれば良い人には向いていますが、それ以外の利用者には適切ではありません。同様に、.ruby-versionを使う場合でも完全にバージョンが一致すれば使えますが、そうでないと難しいです。そして、構造上の決定的な問題として突然バージョンが上がって古いのが消えます。なお、この問題に関する諸事情が次章以降のメイントピックとなります。

最後の自前ビルドですが、これはとにかくビルドすればいいだけなので最も汎用性の高い方法です。欠点は他の選択肢がバイナリ利用であるのに対して、当然ですがソースからビルドするために時間がかかることです。ビルドに必要なライブラリのインストールも行わなくてはなりませんし、毎回テスト走らせる度にビルドしていては遅くてたまらないためactions/cache の利用も必須です。先程紹介した setup-rbenvではactionでやれる範囲でこれらの作業を利用者が直接行わなくても良いようにしています。

setup-rubyがセットアップするRubyはどこから来るのか

setup-rubyがやっていること

さて、ここからは「GitHub公式のactionであるsetup-rubyがなぜベストじゃないのか」というところについて説明していきます。まずはsetup-rubyのコードを追っていきましょう。setup-rubyはTSで書かれていて、エントリーポイントとなる main.ts と そこから呼ばれる installer.ts の2ファイルだけで構成されています。main.ts はパラメータの処理をしているだけで、actionを呼び出したユーザから指定されたバージョンのRubyをセットアップしているのはinstaller.tsです。

https://github.com/actions/setup-ruby/blob/e3283467a12f1688cc3881d94256156faf15666f/src/installer.ts

上記のコードを見てもらえればすぐわかりますが、やっていることは @actions/tool-cache をimportして指定されたバージョンのRubyfind 関数で探しているだけです。そのため、ユーザが使いたいバージョンのRubyVM上にあるかどうかについて setup-rubyは一切関与していないのです。

tool-cacheがやっていること

ではこのtool-cacheとはなんなのでしょうか。これはGitHub Actions用のaction(わかりづらくてつらい)を作るためのライブラリ(@actions/toolkit)が提供しているツールの1つです。

toolkit/packages/tool-cache

tool-cacheがやっていることは、READMEにあるように任意のファイルをVM上に保存することができるというものです。が、名前が非常にミスリーディングで、テスト間でキャッシュしてくれるわけではないです。つまり、actions/cacheでやってるようなことは一切やってくれず、実際にやってくれることはOS上に外部からダウンロードしてきたバイナリを配置(必要があれば圧縮形式を展開)してそのパスを取得することができる、というだけです。READMEのサンプルではnodeをサンプルとし、tool-cacheがOSの違いを吸収して簡単に圧縮ファイルを落としてきて展開&配置できる例が上げられています。

virtual-environments

そして、このtool-cacheには各VMにデフォルトで様々なツールがインストールされています。そのツール群を管理しているのが actions/virtual-environments です。

github.com

実際にtool-cacheの参照先にvirtual-environmentsがソフトウェアをインストールしているのかどうか、見てみましょう。以下は私が setup-ruby向けのPRを作ったときに動作確認用として作ったリポジトリのテスト結果です。

https://github.com/masa-iwasaki/setup_ruby_test/commit/31bca9107dea4dcb079fcd7a70bc98a0426ef6c9/checks?check_suite_id=294590545

テスト結果の Set up Ruby with .ruby-versoin という項目を開くと以下のような実行結果が表示されます。

f:id:masa_iwasaki:20200105170002p:plain
tool-cacheのリンク先

/opt/hostedtoolcache というのがもうそのままな名称ですが、このパス名で virtual-environments のソースコードを検索すると、いくつかのファイルが引っかかります。そのうち、Ubuntu 18.04 向けのtool-cacheインストールスクリプトが以下になります。14行目に先程のディレクトリ名が記述されていて、ハイライト箇所でRubyをインストールしているのがわかりますね。

https://github.com/actions/virtual-environments/blob/95954b3f022a1c4626d112bd4fe82d0812fa6f4b/images/linux/scripts/installers/1804/hosted-tool-cache.sh#L50-L54

なお、hosted tool cacheにインストールされているソフトウェアのバージョンは自動的に以下に反映されているようです(手元で動かしていないので推測です)。

https://github.com/actions/virtual-environments/blob/95954b3f02/images/linux/Ubuntu1804-README.md

virtual-environmentsの抱える問題点

ここまでで actions/setup-ruby が actions/toolkitのtool-cache を通して actions/virtual-environments がインストールしてあるRubyを参照できるようにsymlink貼っている、ということがわかりました。さて、ここからが本題です。なぜ actions/virtual-environments の Rubyは古いのでしょうか。

理由はシンプルで、virtual-environments のrepoにRubyのバージョン変更が反映されていないからなのですが、なぜそれが遅れているのかが謎のままです。このrepoにPRはすでに出ているのですが、マージされていません。

actionsGitHubが管理しているorganizationなので素早い対応が期待されるところですが、動きが遅い理由は不明です(GitHub本体が使ってるRubyのほうが新しいのでは...)。

とはいえ、これには構造的な問題もあるのではないかと考えています。virtual-environmentsが用意するデフォルトのtool chainをPRで受け付けるという仕組みは結果的に「全部入り」になる恐れがあります。READMEにはpopularなものの最新だけサポートするなど一応の基準がありますが、ルールとしてはかなり曖昧です。この基準に基づいてissueやPRを捌きつつ、様々なソフトウェアのアップデートに対応するというのは無理ゲーのような気がします。

また最新のバージョンしか維持しないという方針により、Rubyについてはメンテナンスされているバージョンについてパッチレベルが最新のものだけをインストールするという方針のようです。これはこれで正しい判断だとは思うのですが、利用者からすればある日突然利用可能なバージョンが変わってCIが落ちるということになります。特に .ruby-versionでパッチレベルまで指定している場合、hosted tool cacheに入っているものとバージョンが変わったらどうしようもなくなってしまいます。

事態解決に動くRuby comitter達

ここまで見てきたように、actions/setup-rubyで最新のRubyバージョンが入らなかったり、突然古いパッチレベルのものが使えなくなったりということが起こるのは actions/virtual-environments が原因であるのですが、これを知るためにはソースコードを追っていく必要があるため全員がやるかというとそういことはなく、結果的に「バージョンX.Y.Zがサポートされていない」というissueが立ち続けることになります。そして、なぜか actiosn/setup-ruby のコミッターも反応が薄く、実質放置プレイの様相を呈していました。

そんな中、昨年末に「このレポ死んでるの?」的なissueが立ちます。

github.com

すると、上記issueでRubyコミッターでありFalconの作者である@ioquatix がメンテナーに名乗りを上げ、それに続いて同じくRubyコミッターでTruffle Rubyの開発者でもある @eregon も参加して actions/setup-rubyMRI以外も含む異なるバージョンのRubyをサポートする方法について議論が進んでいます。actions/virtual-environmentsに関する話や、Windows上でのビルド、self-hosted runnerの話なども出ているため、興味がある方は一通り読まれると面白いと思います。

github.com

現在提案されているプロトタイプの仕組み

さて、最後に上記issueで @eregonが提案したプロトタイプについて見ていきたいと思います。彼の提案は2つのリポジトリに分かれています。1つ目が ruby-install-builderです。

github.com

このリポジトリには実質的にGitHub Actionsのworkflowのみが含まれています。このworkflowはruby-installを利用して、様々な実装(現時点ではMRI, JRuby, TruffleRuby)・バージョンのRubyをビルドし、生成されたバイナリをReleasesに登録します。つまり、このリポジトリが actions/virtual-environments の代わりを果たしているということになります。

https://github.com/eregon/ruby-install-builder/releases

この生成されたバイナリを利用するのが2つ目のリポジトリになる use-ruby-action です。

github.com

use-ruby-actionはactions/setup-ruby の後継に当たるものといえます。ruby-install-builderのreleasesにあるビルドをtool-cacheを使ってダウンロードし、ホスト上に配置しています。

この2つのリポジトリで構成されている仕組みは非常に良く出来ていて、本記事で紹介した actions/setup-ruby, tool-cache, actions/virtual-environments の関係性を理解した上で引き続き tool-cache を適切に使うという点で非常に優れた実装だと思います。先程紹介した#44 で @ioquatix が以下のように評していますが、私も同感です。これを quick prototype と言って作ってしまうのはかっこよすぎですね。。

They seem well put together, have good separation of concerns, and should be reliable and "scalable" (i.e. support different versions of Ruby) into the future.

また、このアプローチは actions/virtual-environments のhosted tool cache依存になっている他のソフトウエアを独立させる上でも参考になると思われます。

今後の Ruby on GitHub Actions について

use-ruby-actionのアプローチがsetup-rubyに採用されれば、一番の懸案であった actions/virtual-environments 依存が解消されます。また、今回の流れにより利用可能なRubyについてRuby comitterが直接関わってくれるようになったため、バージョンアップ等の対応も迅速になっていくと思われます。しかし、まだいくつか問題が残っています。

まず、EOL切れたバージョンについては対応しなさそうな気配があります。その場合は自前ビルドかdocker imageの利用しか選択肢が残らない可能性があります。直近だと、2020/03末でメンテナンスが切れるRuby 2.4が必要なプロジェクトなどはこれに該当してくるでしょう。

また、パッチバージョンをどこまで対応するかという問題もまだ議論されておらず、それに関連する .ruby-version に関する話もまだ出てはいません。どちらかというとビルドマトリックス視点で話が進んでいますね。元々自分が出したPRはこの .ruby-version 対応のものだったのですが、ソースコードを読んでいくうちに「根本的にsetup-rubyで .ruby-version対応してどうこうなる問題じゃないな」とcomitterにリマインドもせず、こちらも放置状態でしたが、今後メンテナーの活動がアクティブになり、issueやPRが閉じられていくことになると思うため、その流れの中でリマインド or 新実装に対するPRを出す機会があるかなと考えています。