PerlerのRuby日記

Rubyとか

Ruby 3.2 で WASI の WebAssembly を Docker で動かす

www.ruby-lang.org
docs.docker.com

Ruby 3.2 がリリースされて、WebAssembly や WASI が動くようになったのと、Docker Desktop に WebAssembly ランタイム(WasmEdge)が統合されたりしたので両方やってみたメモ。

公式 README

ruby.wasm/README.md

依存

Dependencies: wasi-vfs, wasmtime

wasi-vfs

Ruby の WebAssembly は Ruby 本体の wasm ファイルも必要になるらしい。wasi-vfs を使ってその Ruby 本体とスクリプトを合体するとのこと。

なので、まずはそれを可能にするツールである wasi-vfs をインストールする。macOS なので手っ取り早く Homebrew 使ってしまった。

$ brew tap kateinoigakukun/wasi-vfs https://github.com/kateinoigakukun/wasi-vfs.git
$ brew install kateinoigakukun/wasi-vfs/wasi-vfs

が、Rust での cargo install が走るので普通にバイナリの Zip を解凍する方が早そうだった。

Wasmtime

WASI ランタイムの実装のひとつである Wasmtime も先にインストールする。今のところ、Wasmtime はランタイムの五強の中で僅差で一番使われている位置にいるらしい。

ref. https://blog.scottlogic.com/2022/06/20/state-of-wasm-2022.html

$ brew install wasmtime

Ruby で WASI の WebAssembly を動かす

README どおりやってみる。

$ curl -LO https://github.com/ruby/ruby.wasm/releases/latest/download/ruby-head-wasm32-unknown-wasi-full.tar.gz
$ tar xfz ruby-head-wasm32-unknown-wasi-full.tar.gz
$ mv head-wasm32-unknown-wasi-full/usr/local/bin/ruby ruby.wasm

$ mkdir src
$ echo "puts 'Hello'" > src/my_app.rb

$ wasi-vfs pack ruby.wasm --mapdir /src::./src --mapdir /usr::./head-wasm32-unknown-wasi-full/usr -o my-ruby-app.wasm

$ wasmtime my-ruby-app.wasm /src/my_app.rb
Hello

できた。my-ruby-app.wasm は、約 32.8 MB となって、やはり Rust などで出す wasm よりもちょっと大きい印象。

Ruby で WASI の WebAssembly を Docker で動かす

作った my-ruby-app.wasm を Docker Desktop で動かしてみる。

Docker Desktop の v4.15.0 から WASI ランタイムの実装の1つである WasmEdge が同梱されたが、まだデフォルトでは有効になっていないらしく、設定の「Features in development」から「Use containerd for pulling and storing images」のチェックボックスにチェックを入れて再起動する。

※ 注意メッセージにあるとおり、現在は、WebAssembly が扱えるようになる代わり、既存の所持イメージやコンテナが消えてしまう。消えたように見えるが、実際には消えていなくて、再度チェックボックスのチェックを外して再起動すればちゃんと残っている

Dockerfile
FROM scratch
COPY ./my-ruby-app.wasm /my-ruby-app.wasm
ENTRYPOINT ["my-ruby-app.wasm"]
Docker ビルド
$ docker buildx build \
    --platform wasi/wasm32 \
    -t wasi-docker:ruby_wasm .
Docker 実行
$ docker container run --rm \
    --runtime=io.containerd.wasmedge.v1 \
    --platform=wasi/wasm32 \
    wasi-docker:ruby_wasm \
    /src/my_app.rb
Hello

できた。Gemfile で色々ライブラリを使ったときはどうなるんだろう。

Rails のキャッシュストアとセッションストアに Redis を使う

Ruby 2.7.1 + Rails v6.0.3.2 + Redis


Gemfile

...
gem 'redis'
gem 'hiredis'
...


開発環境用の設定

config/environments/development.rb

  # Enable/disable caching. By default caching is disabled.
  # Run rails dev:cache to toggle caching.
  if Rails.root.join('tmp', 'caching-dev.txt').exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true

    #--------------------------------------------------------------------
    # config.cache_store = :memory_store
    config.cache_store = :redis_cache_store, {
      url: 'redis://localhost:6379',
      expires_in: 30.minutes,
      namespace: 'foo_cache',
    }
    config.session_store :cache_store, key: "foo_session",
                                       expire_after: 1.day.to_i
    #--------------------------------------------------------------------

    config.public_file_server.headers = {
      'Cache-Control' => "public, max-age=#{2.days.to_i}"
    }
  else
    config.action_controller.perform_caching = false

    config.cache_store = :null_store
  end

キャッシュを有効にする。

$ ./bin/rails dev:cache
Development mode is now being cached.

参考:
railsguides.jp
railsguides.jp

AWS Lambda Ruby 2.7 の puts の返却値は nil ではなく文字数

メモ。

AWS Lambda でランタイムに Ruby 2.7 を選択する。そのとき puts を使うと返却値は nil ではなくて、出力した文字数が返る。

AWS Lambda

# lambda_handler.rb
require 'json'

def lambda_handler(event:, context:)
  ret_puts = puts "foo!!!!!"
  puts "ret_puts: #{ret_puts.inspect}" #=> 8 ("foo!!!!!".length)

  { statusCode: 200, body: JSON.generate('Hello from Lambda!') }
end

ローカルで irb したとき

2.7.1 :001 > ret_puts = puts "foo!!!!!"
foo!!!!!
2.7.1 :002 > puts "ret_puts: #{ret_puts.inspect}"
ret_puts: nil
 => nil


ちなみに、ランタイムに Ruby 2.5 を選択すると、puts は nil が返る。

参考:
docs.ruby-lang.org