ついにCloud FunctionsにRuby Runtime登場!

今さら感がないと言えば嘘になるけど、

Ruby comes to Cloud Functions | Google Cloud Blog

ついに、Cloud Functions で Ruby が使えるようになりました! いやー長かったな。

個人的には closed beta の段階で申し込みはしてて動かすことだけはできていたのに結局試す時間がないまま public beta になってしまって申し訳ないという気持ち半分、いやでも単純に嬉しいという気持ち半分です。

何はともあれ、これで Ruby の実行環境にカジュアルな FaaS の二つ目1が加わったわけです! いやめでたい。

というわけでこれまでの他の言語での経験と Ruby / Rack / Ruby版 Functions Framework の特徴をざっと眺めて、実際のプロダクトコードを書く際に気を付けることなどに触れて紹介に代えたいと、思います!

Functions Frameworkというものがあるよ

Ruby で Function を書く際には Functions Framework というものを利用する。

GoogleCloudPlatform/functions-framework-ruby: FaaS (Function as a service) framework for writing portable Ruby functions

closed beta の頃にはすでに存在してて、こいつが何をするものかというと、

  • Cloud Functions, Cloud Run または Knative ベースの仕組みの上で動かせる
    • Cloud Run は Knative ベース
  • local での開発にも利用できる

という代物で、先行している Node.js や Python 版と同様の役割を果たす。すでにこれらの言語で経験のある人には説明の必要はないんだけど、

特徴的なのは GCP 上のイベント(代表的なものは PubSub)に対応する関数も HTTP 関数も同等に扱える

点。

どういうことかと言うと、Functions Framework の中身を見ると分かるのだがイベントも頑張って Rack::Request に変換してくれているおかげで、対応するすべてのものを Rack アプリで処理できるようになっている。

つまり Ruby で HTTP のサーバサイドを扱う人には最も馴染みの方法で HTTP 関数だけでなく様々なアプリを書けるようになっているということです。素晴らしい。

Functions FrameworkやFunctionsの不便な点

ただ、Functions Framework は Node.js なんかもそうなんだけど、注力しているのはこの request の source の抽象化であって、実際のアプリ内の話については重視していない。そこで以下にいくつか不便な点を挙げていこうと思う。

reloaderの問題

Ruby では多くの場合でフレームワーク側で reloader を用意してくれていて、Sinatra も Rails も開発環境ではいい具合にソースコードの変更に応じて reload してくれるのだが、Functions Framework はそういう部分をケアしてくれるものではない。

ではどうするかと言うと、方法は大きく二つあって、

  1. Functions Framework を Sinatra など reloader を持つ framework と組み合わせる
  2. nodemon のような汎用の reloader ( restarter ) を利用する

1 の方がたぶん慣れている人には分かりやすい。具体的にどのように Function を書くかについては公式に記述があって、

File: Writing Functions — Functions

にあるように、Sinatra などと組み合わせることができる。使い方としては要は Rack アプリに対して Rack アプリが期待する env を渡してやるだけである。公式の Sinatra サンプルは class ベースの modular style アプリだが、class を定義しない classic style でも同様に利用できる。

そのうえで、Sinatra のアプリケーションコード内で sinatra/reloader を require してやれば普通に development 環境では reload が有効になる。

ただ、後述するが実際には典型的な人間向けの Web アプリの実装に向いているこれらのフレームワークを利用する機会はそんなに多くないように思う。その際はフレームワークではなくこのあとに述べる reloader と組み合わせるとよい。

nodemon

nodemon はもとは node コマンドを置き換えて

$ node app.js

の代わりに

$ nodemon app.js

のように使うとファイルの変更を検知して自動的にサーバを restart してくれるというものだったが、watch する拡張子と実行するコマンドを指定すると Ruby アプリでも問題なく利用できる。具体的には

$ nodemon -e rb --exec "functions-framework-ruby -t <func>"

のようにしてあげるとよい。ここに書いたものは npm run や bundle exec は省略してあるので必要に応じて適宜追加してほしい。

functions-framework-ruby 0.7時点でのNode.js版1.6との違い

functions-framework-nodejs は body-parser を含んでいたりするので、素の express の request, response を扱うこととは異なり、もう少し便利な機能が備わっている。

対して functions-framework-ruby 0.7 時点では今のところ追加の支援はなさそうなので、かなり生々しい Rack オブジェクトを扱うことになる。現実的なアプリを書くには、例えば比較的全部入りのアプリケーションフレームワークと組み合わせる2か、あるいは

を使いつつ

のような機能を追加して対応していく感じになるのかな。こっちのアプローチの方が express っぽいけど、Rails も Sinatra もあまり軽くはないので、特にリソースの制約の厳しい Functions 環境ではこれくらいの生々しさや軽量さへのこだわりはあってもいいかも。

cf.

Functions Framework独自の情報をどう引き回すか問題

これは Functions Framework が担う部分と従来の Web アプリケーション Framework の担う部分の違いの話。

Rails などの通常の Web アプリケーションフレームワークは request, response の入出力の方法からアプリケーションロジックに当たる部分、DBMS の読み書きの部分までトータルにサポートするのが一般的3と言える。

対して Functions Framework の責任の範囲はあくまで HTTP と CloudEvents を抽象化し、Rack 互換のアプリケーションで処理できるようにするための繋ぎの部分であって、それ以外は対象外となっている。

そのうえで、Functions Framework 固有の情報もある。例えば global, set_global というメソッドは Functions Framework 内だけで利用できる global 変数のようなもので、これを利用すると以下のように request を受けるたびに実行するには重たい初期化処理を行ったり、その結果の情報を保持しておくことができる。

FunctionsFramework.on_startup do |function|
  set_global :config, heavy_initializing_process()
end

FunctionsFramework.http <name> do |request|
  config = global :config
  ...
end

ただこれが通用するのは Functions Framework 内の話であって、例えば先ほど挙げたように Sinatra と組み合わせるような場合には Sinatra の中ではこの global にアクセスすることはできない。

そこでどうするかというと、Sinatra アプリに唯一渡せるのは rack env オブジェクトなので、この env の中に global をぶら下げる形になる。先ほどの例で言うと、

FunctionsFramework.http <name> do |request|
  config = global :config
  env = request.env
  env.config = config

  Sinatra::Application.call env
end

のようになる。

Functionを書く際に考えなければいけないことは意外に多くなる

これも別に言語は関係なくて、今まで FaaS を使っていた人にとっては割と当たり前の話。

実際に Cloud Functions を書く際には恐らく従来の人間向けの Web アプリよりも小規模で、かつ逆にインフラにより近いものを直接扱うようになると思う。インフラというのは例えば Cloud Storage, Firestore などのストレージだったり、PubSub や他の Function など、Google Cloud 上のフルマネージドインフラ。Functions を選ぶということは GCE ではないということで、それはつまりアプリケーションの実行環境から直接同じ OS 上で管理できるような伝統的なインフラが存在していない、という意味になる。だからフルマネージドインフラのお世話になるだろうという推測である。

で、そうなると Sinatra や Rails などのアプリケーションフレームワークの機能や構造の分け方などはあまり役に立たない。これらの便利機能は主に

  • Cookie や Session
  • HTML や response の文字列(JSONなど)の生成
  • (DBMSアクセスの抽象化)

は担ってくれるが、それ以外のインフラを扱う部分に対しては基本的には管轄外となってしまう。

例えば Memcached や Redis を利用する際にそのコードをどのように配置すべきかについては Rails には答えはなくて、利用用途がほぼ cache だろうから Rails.cache のバックエンドに置くことを支援してくれるまでに留まっている。実際には cache を扱うコードをどこに置くべきかについてはガイドがないので、作成および更新処理と読み込む処理が分散してコントロールが難しくなってしまうという問題を抱えていたりする。

こうした課題への対策には恐らくいわゆるアプケーションアーキテクチャを考えることになるだろう。いわゆるオニオンアーキテクチャやクリーンアーキテクチャと呼ばれる類のアレだ。

Functions は周知の通り実行時間や利用できるメモリに制限があり、あまり複雑な機能を実装することはできないが、代わりにいわゆる Web MVC のようなシンプルな構成とは異なり、アプリケーションの扱わなければいけないインフラが複雑になりやすい傾向があるので、その分で考えることは増える。

特に異なるインフラを扱うコードが密に結合してしまうと「本番でしかテストできません」みたいなことが容易に起き得るので注意が必要である。

注意深くインフラとロジックを分離し、依存の方向に気をつけて DI で組み立てていくようにする、そういうコードの量が増えるはず。

参考

  1. 一つ目はもちろん AWS Lambda 

  2. 当然、spin upが重すぎるのでFunctionsには向かない 

  3. Ruby を日常的に利用している人には当たり前の話になるが、基本的に request, response は Rack の作法に従うことでその部分の独自実装をあまり行わないようにするのが一般的であり、フレームワーク独自の部分はあまりない。 

PHP で curl 使ってみた

例によって変な落とし穴が。

CURLOPT_RETURNTRANSFER
TRUE to return the transfer as a string of the return value of curl_exec() instead of outputting it out directly.

ということは

curl_setopt( $curl_object, CURLOPT_RETURNTRANSFER, 1 );

してやらないと curl_exec() した瞬間に内容が stdout へ吐き出されてしまう。

この仕様で嬉しい人いるの?

分からんなぁ。デフォルトはできるだけ広く嬉しい設定にしておくもんじゃないの?

php の --r 系オプション

PHP 4 時代には存在しなかったので気にしたことがなかったけど、PHP 5 には –r 系のオプションがある。

 --rf <name>      Show information about function <name>.
 --rc <name>      Show information about class <name>.
 --re <name>      Show information about extension <name>.
 --ri <name>      Show configuration for extension <name>.
$ php --rf array_map
Function [ <internal:standard> function array_map ] {

  - Parameters [4] {
    Parameter #0 [ <required> $callback ]
    Parameter #1 [ <required> $arg1 ]
    Parameter #2 [ <required> $arg2 ]
    Parameter #3 [ <optional> $... ]
  }
}

おぉ、便利じゃん。と思ったけど

戻り値が分かんないじゃん、これ。

そりゃないよ。そりゃーない。それに引数の方もちゃんと全部説明が出るのかと思ったらそうとは限らないみたい。あーなんだよもう。

やるならちゃんとやってくれって。頼むから。

CentOS の zsh で svn の補完

svn を rpmforge でアップグレードしたら zsh の補完がぶっ壊れた。

と思ったらこの件は有名なものらしい。

CentOS 5.2 で Subversion 1.5.x にアップデート・zshの補完も修正 - 肉とご飯と甘いもの @ sotarok

この辺がねぇ。中途半端にあちこちのリポジトリに頼らなきゃいけない rpm 系の弱いところだよね。

Ruby のモジュールをつらつらと

眺めてみる。いやその管理用のツールをもうちょっとまともに用意しとかないといかんなーと思ったもんで。昔なら問答無用で一から作るって方向になった1と思うけど今は面倒くさいの(と、他人のコードを読んでみたいという欲求)で、モジュールをちょろちょろ眺めている。と言ってもまだ RAA と RubyForge で検索掛けたくらいなんだけど。

※ 何が使えるもので何が違うのかまださっぱり分かっていないのでツッコミは絶賛大歓迎中です。

メール周り

  • RAA - mailparser
    • mailread よりいいんだと思うけどまだよく分からず。raccが必要。ただまぁそんなに本格的なものが要るかって言われたらそんなことないとは思っている。
  • RAA - scarlet
    • まだ読んでないんだけど、管理系のメールの取り回しにひょっとしたらすごく役に立つのかもしれない。
  • RAA - classifier
    • まだよく分かってないんだけど、メッセージの分類に便利かもしれず。

メールの処理は今のところ Thunderbird 一辺倒でこれと言って工夫していないので、そこを反省してできるだけ自動化できるようにしていきたいな2。Feed に変換するっつーのもいいかなと思ってはいる。それぷとか言わずに Perl は触りたくないので Ruby で行く方向で。

スケジュール周り

人間用のスケジューラじゃなくて機械のスケジュールを分かりやすくしたい方向で。

  • RAA - crontab
    • どこまで使えるのか分からないけど、とりあえずメモ。
  • vpim - a library to manipulate vCards and iCalendars
    • 実行予定を iCal 形式にできたら見やすそうだなぁと。複数の機械のスケジュールを iCal でオーバーラップして表示させたら見やすくないですか。データの整形さえできれば見た目をどう整えるとかクライアントの操作性とか全然考える必要ないのもステキ。ただ実現できるかどうか全然検討してません。

あと個人用途では crontab コマンドの wrapper もあったらいいかなと思うのはこの間書いた通り。

ファイルとか

ここら辺は管理は考えてなくて、使ったら便利そーとか面白そーって感じで目についたもの。

  • RAA - hstore
    • スレッドセーフな PStore
  • RubyForge: FSDB: Project Info
    • ディレクトリの中にファイルを配置していく PStore みたいなもんかな? なんか Gauche の組み込みにこういう働きのものがあってうらやましかった記憶がある。
  • RAA - find2
    • depth が使えるのはいいな

経験上、どマイナーな日記ではこうして挙げておいてもツッコミで情報やノウハウを補完できないのがつらいところだな…。

  1. そして挫折して放置 

  2. imap の件はこれとはまったく関係ないプライベートの話。 

XREA+

「有料広告免除サービス」のサービス名変更について (xrea.com)

へぇ。まぁそうだよね。オレもディスクスペースが20倍になってるとか知らなかったし。

無名関数ってないのかーと思ったら匿名だった

気になるコトバ - ときどきの雑記帖

匿名関数って言葉は変じゃねーかという話。まったくそうだなぁと思ったのは、最近 PHP で無名関数ってどうすんだっけと調べていて見つからなかったから。1匿名ってなぁ。

で、「PHP 匿名関数」でぐぐってみると

FreeML メッセージ デザインパターン・メーリングリスト - 1005

PHP でイテレータパターンを書く方法が出てきたんだけど、どっかで見た名前だなーと思ったら高橋メソッドの中の人ですね。そうか、ほんとに PHP 使いなんだ、高橋さんて。

…大変すね。

  1. ローカルに落としてあるマニュアルを検索していた。 

今度こそ rsync

なんかね、この2年ほどで rsync に関するメモを4回くらい書いてるんですよ。えーと、必要なファイルだけを転送したい場合は

  • exclude=* を最後に加えて include= で必要なファイルを並べる
    • include を exclude より先に書く
    • サブディレクトリを扱う場合は include=*/ が必要
    • 現実的には –include=*/ –include-from=FILE –exclude=* か
    • それでも空のディレクトリが転送されることを阻止することはできないし、使いにくさは残る
  • include や exclude で使えるパターンはシェルのワイルドカードらしき独特のもの
  • サブディレクトリを扱う場合は exclude パターンに注意
  • -from= でファイルにパターンを書く場合はどうやら1行1ファイルらしい

定期的に違うパターンで転送する練習しないとダメかなぁ。何回やっても身につかないツール No.1 rsync.

あ、ちゃんと Wiki に書かないからいかんのか。

どうして「知人」なのかなぁ

紳助暴行事件 衝撃の一部始終(1)

なぜ弁護士じゃないんでしょうか。もったいないなぁ。知人とかある筋とか関係者とか誰だか分からない人の情報を無記名で載せられてもやっぱ信憑性ないじゃないですか。

どこかでフェーズのシフトは起きるだろう

「プログラマ35歳限界説」の真偽

いきなりコメントに注目。

自分はある意味本当だと思う。 前提条件としては年中デスマーチをやらかしているところで働いている事。

20代では体力と記憶力で、30代前半は経験を活かしてしのいでこれるが、 30代半ばになると非常にアバウトな言い方になりますが、仕事をこなしていくのに必要なモノがアウトプットよりインプットが減ってしまって 中身がスカスカになってしまうからだと思うのです。

デスマーチがあろうがなかろうがどの業界でもどこかで”スカスカ”って起こり得ると思うし、むしろソフト産業はまだ(携帯とか)歴史がくり返している部分があるんで、ひところ言われたより寿命が延びるという現象はこの先もあるような気がする。

一方で使えないなんちゃって技術者の淘汰は遠くない将来に起きないと業界全体的にまずいような気もしているけど、これも昔から素人とプロの境界が比較的曖昧な業界だったような気もして、そこまで含めて歴史がくり返しているのかなぁとなんだか分かったような分からないようなことを思う。

うーん、もともと業界全体に対して悲観的なのが再確認できただけか?

DirectoryStorage 諦めモード

目的は Zope でサイト運営することではないので、代替手段を模索し始める。要はバックアップの時間がべらぼうに掛かっちゃったりしなければよいのだ。どうせバックアップは取るのだ。ちゃんと二重に。

やはり DirectoryStorage しかないのか?

昨日の URL、CVS のもの以外は試してみました。結果、AdaptableStorage はよくできていて、扱いやすいです。が、結局 history を遡れないということで、求める機能が実現できないので却下でしょうか。ExternalFile とか ExtFile とか、どれも多少操作方法が違うだけで、たいした違いはないように感じました。今後の将来性については AdaptableStorage にいちばん期するものがありますけどね。現状ではちょっと。

それにしても DirectoryStorage にしろ subversion にしろ、ちょっと挑戦するのが早かったですかね。基本的にわたしゃ自力 build のスキル低いし、cygwin も実のところよく分かっておりません。まして Visual C / MinGW をや。

About

例によって個人のなんちゃらです