HerokuのCloud Native Buildpacksを使ってCloud RunでSinatraアプリを動かしてみた

動機

やっぱりRubyからカジュアルにJavaScript runtimeを呼びたい

この10日間くらい、Ruby から JavaScript のライブラリの解釈の結果を取得できれば助かるなぁと考えていた。

ProseMirror/prosemirror-model: ProseMirror’s document model

Promise Mirror というブラウザ上の WYSIWYG エディタを作れるキットが自身のデータ構造をもとに HTML に変換する処理を、JavaScript 以外から利用できればいろいろできること増えるよなぁという話。

脱線を含めて経緯としてはこんな感じ。

  • Node.js のフル仕様は要らないから Duktape で処理できないか?と思ったけど Vite から esbuild で ES5 に変換する処理が stack level を超えて断念
  • フルマネージド環境で異なる言語 runtime をミックスして使うってイマドキはあんまり流行らないんじゃないかなぁ…。docker compose 的な感じで別プロセスの Node.js か何かに投げて処理するのがいいのか?
  • Dockerfile も compose.yaml もメンテしたくないんだ!
  • そう言えば Heroku のもとになった Cloud Foundry みたいなものを使えばやれるのでは?
  • 今はもう Cloud Native Buildpacks ですよ

ということで Cloud Native Buildpacks でやりたいことができるか試してみたよ。

※ 実は去年 Docker の multi-stage build を調べていたときにこの Cloud Native Buildpacks が multi-stage build を利用していることで理解が深まったんだけど、当の Buildpack の利用に思い至らなかった。どうして…。

Ruby x Node.js x yarnを利用した静的サイト生成用のDocker imageをできるだけ素朴に構築する (2022-05-03) | あーありがち

結論

Cloud Native Buildpacks · Cloud Native Buildpacks

  • Cloud Native Buildpacks を使えば Heroku のように(一定の規約を守った)アプリケーションのコードさえあれば Docker image を作ることができる
  • Docker image からコンテナを実行できる環境さえあればどこでも同じように動作する(注意点アリ)
  • さらに継続的にメンテされている Buildpack を使っていれば、自分で Dockerfile をメンテしなくても自動的にセキュリティなどの対策を期待できる
  • つまり、Heroku のような環境のうち、実行コンテナイメージのビルドまでの部分については、どこの実行環境を利用していてもその恩恵に与れる

これで Dockerfile とおさらばだ!

成果物

まずはじめに成果物のリポジトリ。この中身を覗いて、動かしてみるとやりたいことがわかると思う。 

wtnabe/example-cnbp-node-ruby

Cloud Native Buildpacksとは

リンク先に詳細な解説があるので参照してもらうといい。

Cloud Native Buildpacksの主要なBuilderを調べてみた - ISID テックブログ

あとは蛇足。

かつて一世を風靡したHeroku buildpackとは

Cloud Native Buildpacks の情報に当たると特に buildpack という言葉が何回も出てきてややこしいので、そこだけ。

もともと自分は Heroku の buildpack に興味があって、その時はこうなっていた。

  • buildpack とは特定のツールを導入するための手順と実際のインストール用のコードが収まったもの
    • 例えば Ruby のインストールや Nginx を前段に置くといったセットアップはこの buildpack を用意することで済ませていた

実際の buildpack の挙動は以下のような手順を踏む。

detect
リポジトリ内のコード上のルールをもとに必要な言語 runtime を特定
compile
所定のルールに従ってアプリケーションを実行可能な状態にビルド

detect 時に成功の status を返せば compile で実際に言語をインストールする、といった具合になっていて、Nginx みたいなアプリケーションの実行言語に関係なくインストールしたいものは detect を常時成功扱いにする、といった感じで調整する。

今の Cloud Native Buildpacks でも基本は同じで、

  • detect
  • build

と呼び方が変わったようだ。

Builderとは

上に書いた buildpack にはアプリケーションの実行を担う OS 部分の話が含まれていない。Heroku の場合、Stack と呼ばれるもので OS やそこにインストールされるツールが決まっており、例えば Heroku-22 stack は Ubuntu 22 LTS をベースに Heroku が調整したものになっている。

Cloud Native Buildpacks における Builder は

  • buildpack を束ねる
  • ベースイメージを規定する
  • その他

以上の役割を担う。Ruby buildpack を含むものとしては具体的には以下のような Builder がある。

今回やったこと

手慣れた Ruby アプリで試したかったので、Ruby の buildpack があると分かっていた Heroku の builder を利用した。その時ハマったことは最後の注意点に挙げるが、期待通り 「アプリケーションのコードの様子から自動的に」Docker image がビルドされ、できあがった Docker image は普通に Heroku でも Cloud Run でも動作した。

ということで少なくともよくある Web アプリについては Docker image を作るために Dockerfile を書く必要はなくなった。これはめでたい。

今後の課題

  • web プロセスしか動かしていないので worker がどうなるか確認したい
    • Heroku の場合はそのまま Dyno になってくれそうだが Cloud Run は Service ではなく Job にする必要があるはず

build時の注意点

Heroku builder (v3)のRubyのバージョン指定はGemfile.lockで

heroku/buildpacks-ruby: Heroku’s Cloud Native Buildpack for Ruby applications.

v2 までは .ruby-version を参照して動いてくれていたが、v3 では Gemfile.lock でバージョンを指定していない場合は default のバージョンで動作する。これも 2023-12-16 時点の話で、もしかしたらその後変わっているかもしれない。

assets:precompile taskを作ればいい具合に処理してくれる

Sinatra には便利な rake task 群はデフォルトでは存在しないが、Heroku の Ruby buildpack は assets:precompile task があればいい具合に処理してくれるので、手動でこの task に実行させたいコマンドをセットするとよい。

実行時の注意点

Herokuは従来のProcfileのままだと動かない

Procfile には

web: bundle exec puma -c config/puma.rb -p ${PORT:-3000}

のように書くが、今回ハマったのは Heroku の container stack ではなぜか bundle exec puma -c ... っていうコマンドがあるということになっていて、どうしてもサーバの起動ができなかった。

仕方ないので

./bin/start-web

という sh script を用意して上の内容を以下のように作り、

#! /bin/sh

bundle exec puma -c config/puma.rb -p ${PORT:-3000}

Procfile からはこれを呼ぶことにした。

web: ./bin/start-web

Heroku さん、しっかりしてください…。

More