
GCP の FaaS (Functions as a Service)、Cloud Functions がついに Ruby に対応しました!!
AWS Lambda では2018年末の時点で既に Ruby が使えていたので、Cloud Functions にも来ないかな~と思っていた人には朗報ですね。

現在はβ版で、Ruby 2.6 および Ruby 2.7 のバージョンをサポートしています。  
Cloud Functions の公式ドキュメントにも既に Ruby 用のサンプルコードが追加されており、普段 Ruby を書いてる人であればすぐに使えると思います。
Cloud Functions の Ruby サポートと併せて、ローカル環境での Ruby を使った Cloud Functions & Cloud Run 開発をサポートする Functions Framework for Ruby (α版)も公開されました。
- GitHub - GoogleCloudPlatform/functions-framework-ruby: FaaS (Function as a service) framework for writing portable Ruby functions
- Functions Framework for Ruby のドキュメント
作成した関数(スクリプト)のデプロイ方法は他の言語とほぼ変わらないと思うので省略しますが、今回は Functions Framework for Ruby(以下、Functions Framework)を使って、ローカル環境でどれほどスムーズに開発ができるのか試してみました。
gemのインストール
Ruby と Cloud Functions の仲介をする Functions Framework は gem として提供されています。
1
$ gem install functions_framework
HTTPトリガー関数の作成
ここでは Cloud Functions の最も一般的な使い方と思われる、ブラウザ等からの HTTP アクセスによって起動する HTTPトリガー関数 を作成して動かしてみます。
もちろん、HTTP アクセス以外に、Pub/Sub メッセージの受信や Cloud Storage へのファイル保存をトリガーにすることも可能です。
ソースコード内で functions_framework ライブラリを読み込み、 FunctionsFramework.http ~ end ブロック内に、HTTP アクセスされた時の処理を定義します。
1
2
3
4
5
6
7
8
9
#== app.rb ==#
require "functions_framework"
# 関数名: hello_http
FunctionsFramework.http "hello_http" do |req|
  name = req.params["name"].to_s
  "Hello, #{name.empty? ? "World" : name}!"
end
HTTP アクセスによって関数が実行されると、 Rack::Request オブジェクトがブロック引数に渡されます(ここでは req )。  
GET/POST パラメータもこの Rack::Request オブジェクト内に含まれていますので、params メソッド等で取得します。
ここでは GET パラメータから name の値を取得し、 "Hello, #{name}!"(値が無ければ "Hello, World!")を出力します。
ローカル環境での実行
functions-framework-ruby コマンドで、ローカル環境で関数を実行し動作を確認できます。
--source にファイル名(デフォルトは app.rb )、 --target に関数名を指定します。  
gem を bundle install した場合は、 bundle exec を頭に付けてコマンドを実行します。
1
2
3
$ functions-framework-ruby --source=app.rb --target=hello_http
または
$ bundle exec functions-framework-ruby --source=app.rb --target=hello_http
コマンドを実行すると Web サーバ(Puma)が起動するので、ブラウザや curl コマンドでアクセスすると、関数が実行され Rack::Request オブジェクトが渡されます。
ブラウザで http://localhost:8080 にアクセスするとレスポンスが確認できました。
- GET パラメータなし  
  
- GET パラメータあり (name=Ruby)  
  
Web サーバは Ctrl+C で終了します。
テスティングフレームワークとの連携
Minitest や RSpec といったテスティングフレームワークと Functions Framework を連携させて、ローカル環境で作成した関数のテストを行うこともできます。
テストコード内で functions_framework/testing ライブラリを使用します。
- load_temporary- ソースファイルの読み込み
 
- make_get_request- GET リクエスト
 
- call_http- 関数の呼び出し(関数名を指定)
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#== app_test.rb ==#
require 'minitest/autorun'
require 'functions_framework/testing'
class MyTest < Minitest::Test
  include FunctionsFramework::Testing
  # GETパラメータなし
  def test_get_no_param
    # ソースファイル読み込み
    load_temporary './app.rb' do
      request = make_get_request("http://localhost:8080/")
      # 関数名を指定
      response = call_http("hello_http", request)
      assert_equal(200, response.status)
      assert_equal("Hello, World!", response.body.join)
    end
  end
  # GETパラメータあり (name=Ruby)
  def test_get_with_param
    load_temporary './app.rb' do
      request = make_get_request("http://localhost:8080/?name=Ruby")
      response = call_http("hello_http", request)
      assert_equal(200, response.status)
      assert_equal("Hello, Ruby!", response.body.join)
    end
  end
end
前項(ローカル環境での実行)の手順で Web サーバを起動し、別のターミナルから Ruby のテストコードを実行します。
1
2
3
4
5
6
7
8
9
$ ruby app_test.rb
Run options: --seed 34828
# Running:
..
Finished in 0.023939s, 83.5457 runs/s, 167.0914 assertions/s.
2 runs, 4 assertions, 0 failures, 0 errors, 0 skips
無事、テストが成功しました。
まとめ
ちょっとしたスクリプトやバッチをサーバーレスで実行できて便利な Cloud Functions ですが、普段 Ruby を書いてる人にはさらに手軽に使える選択肢になったと思います。  
ローカル環境での実行・テストも gem を導入すれば複雑な設定も不要で、サクッと開発できると感じました。
Cloud Functions には実行メモリやタイムアウト(処理時間)の制約があるため、大幅なパフォーマンスの向上を実現した Ruby 3系にも対応すれば、さらに活用の幅が広がるのではと思いました。  
今後のアップデートに期待です。
