Herokuにバイナリを組み込むbuildpackを作成する

こんにちは

FFmpegというツールをご存知でしょうか?

私は映像関係の技術にはまったく明るくないのですが、動画のフォーマット変換や合成などが行えるライブラリらしいです。

今回このライブラリをHeroku上で動作するようにし、さらにはそれをbuildpack化するという作業をやったので、その過程をご紹介します。

題材としてはffmpegを扱っていますが、自分でbuildしたバイナリをHerokuに組み込む場合は同じ手順でできるはずです。

ちなみに参考にしたURLは以下です。

最初は言わずと知れたDevCenter。次のはBinaryのビルドで使用するBuildServer「Vulcan」のGitHub。最後は過去にffmpegをHerokuに組み込んだ先達の残してくれたコマンドログです。

特に最後のGistは秀逸でHeroku上でffmpegを動かすだけならここに載っているコマンドを上から叩いていけばそれだけで動きます。

心から感謝します。

 

★Vulcanとは何か?

さて、上のGistを見ると最初にgemコマンドでvulcanをインストールしています。

Vulcanとは何かと言うとHeroku上で動作するビルドサーバーです。

Node.jsで作られたサーバーアプリケーションとRubyで作られたコマンドラインクライアントからなります。

この二つが連携して、クライアント側にあるソースコードをサーバーに送って、サーバー側でビルドした結果のバイナリをクライアントに送り返す、ということをやってくれます。

ここでVulcanサーバーがHerokuアプリであることは非常に重要です。というのは今時のLinuxではそれぞれのDistributionでライブラリのバージョンがどうなっているとかの事情がまったくわかりませんし、ましてやHerokuのDynoでベースのOSに対してどのようなカスタマイズが行われているかなど知りようが無いからです。(例えばDynoに「heroku run bash」で入ってもviは使えません。。。)

なので、異なる環境でビルドしたバイナリをそのままHerokuに持って行っても必ずしもそのまま動くとは限らず、Heroku上で動かすバイナリであるならばHeroku上でビルドしてしまうのが一番確実な訳です。

 

★ffmpegをビルドしてHerokuで使ってみる

といっても、Gistにあるコマンドを上から実行していけば良いだけなのですが。(^^;

注意が必要なのはこれらのコマンドは必ずLinuxまたはMacから実行しなければならないという点です。

これには二つの理由があります。

  • vulcanクライアントが内部的にシェルのコマンドを実行しているため、そもそもWindowsでは動かない
  • Executableなバイナリをgit pushする場合はpermissionを維持しなければならないがWindowsでは無視される

ということで、普段はほとんどの作業をWindowsで行っている私も以降の作業はUbuntuで実行しました。(後にbuildpackを作るためにMacでもやり直しました。)

手順としは本当にGistを上からなぞるだけなんですが、一応列記しておくと

  1. gemでvulcanをインストール
  2. vulcanのコマンドでHerokuアプリの作成
    • クライアント側にサーバーのURL等の設定情報が保存される
    • Addon(Cloundant無償版)も同時に追加される
    • このためコマンドを実行するHerokuユーザーにはクレジットカード情報が必須
  3. ffmpegのソースをgitで取得
  4. vulcanでビルド
    • 実行完了時にDownload URLが表示される
    • curlかwgetで完成したバイナリのtgzをダウンロード
  5. 解凍したバイナリを自分のHerokuアプリのフォルダにコピー
  6. heroku configでffmpegを実行するためのPATHとLD_LIBRARY_PATHを設定
  7. heroku にgit push

以上、終わりです。「heroku run "ffmpeg -version"」を実行することでffmpegが正しく実行できることが確認できます。

 

★buildpack化を検討してみる

ここまでの結果からHerokuでバイナリを実行するためには要するに

  • 実行可能なバイナリをコピー
  • PATHとLD_LIBRARY_PATHを適切に設定

すれば良いことがわかります。普通にLinuxを使う場合とまるっきり同じです。

実際に使うことを考えた場合、毎回プロジェクトにバイナリを含めてgit push、というやり方は面倒ですし、先にも述べたとおりpermissionの問題があるのでWindowsからは実行できません。

なのでbuildpackにすることを検討してみます。

buildpack化するにしても例えばJavaのbuildpackにちょっと手を加えて目的のバイナリをインストールするコードを付け加えるという方法はあまり嬉しくありません。

それだとRubyやPythonなどの他の言語を使う際には使えないので。

なのでheroku-buildpack-multiを使って常に他のbuildpackと組み合わせて使うbuildpackとして作成することにしました。

 

★作ってみた

で、できあがったbuildpackがこれです。(いきなり)

heroku-buildpack-ffmpeg

実のところ最初からブログのネタにすることを念頭に作っていたんですが、これが思った以上に簡単でもうソースを読んでもらえばそれで良いんじゃないかと言う気がするんですよね。。。(^^;;;

それでもいくつかポイントになるところはあるので以降それらについて解説します。

 

buildpackの構成

buildpackの実体はbinディレクトリにある3つのスクリプトファイルです。それぞれ以下の役割があります。

detect このbuildpackが実行可能かどうかを判断し、実行可能であれば0を返す
例えばJavaのbuildpackの場合はpom.xmlがあれば0を返すようになっている
compile build処理本体。実行に必要なファイルを収集してBUILD_DIRにぶちこむ
release リリース時に設定する環境変数や追加するアドオンをYAML形式で標準出力に出力する
(環境変数の設定とアドオン追加は初回のgit push時にのみ実行される)

今回の場合detectとreleaseの二つのファイルについては

  • detect -> 常に実行
  • release -> 特に設定項目なし

なのでほぼ空っぽです。
(最初はPATHやLD_LIBRARY_PATHはreleaseで設定すれば良いかと思ってましたが、buildpack-multiがreleaseについては最後に実行したbuildpackの出力を採用するという仕様のため後述する「.profile.d」を使用する方法に変更しました。)

つまり書いたのはほぼcompileスクリプトのみなわけですが、今回の場合ここでやらなければいけないことは以下の2つです。

  • インターネット上のどこかからffmpegのバイナリを取ってきて展開する
  • PATHとLD_LIBRARY_PATHを適切に設定する

あとは何をやっているかがわかるようにちょいちょいechoをだしてやればOKです。(ちなみに「----->」や先頭スペース6個のインデントはHeroku推奨のecho書式です。)

 

GitHubページにバイナリを置く

さてダウンロードするバイナリの置き場所について、Herokuの推奨はS3なんですが、今回はGitHub上に置くことにしました。

その方がバージョン管理も同時にできて楽かなぁと思ったもので。(^^;;;;

当初はmasterにそのまま置いてrawURLでダウンロードしてくれば良いかと思いましたが、実はGitHubのrawURLにはサイズ制限がありました。(何MBだったかは忘れましたが。。。)

なのでGitHubページを作ってそこに置くことにしました。

GitHubページとはGitHub上のプロジェクトに対して(静的ファイルから構成される)専用のホームページを持つことができるというあれです。

本来の用途とは違うのでこちらもそのうち制限がかかる可能性はありますが、mavenリポジトリとして使用している人が何人もいる位なので多分大丈夫でしょう。(^^;

 

「.profile.d」にログインスクリプトを置く

次は環境変数の設定です。

これは最初に仕組みを知った時にはかなり「へぇ~」と思ったことなんですが、Herokuのdynoって本当に普通にログインして普通にコマンド叩いてるだけなんですよね。

なので「.profile.d」にスクリプトを置いておけばDyno起動時にそれが実行されます。

ということで、compileの中で環境変数を設定するログインスクリプトをechoで生成しちゃえばそれでOK。

う~ん、よくできてる。(^^v

 

★Hackingとか

以上完成です。

READMEにも書いてある通り、.buildpacksを用意してbuildpack-multiを使って試してみてください。(注意が必要なのはメインのbuildpackは最後に書かなければならないという点です。先にも少し触れましたが、buildpack-multiは最後に実行したbuildpackのreleaseスクリプトから環境変数等を設定します。)

既存のアプリに後からBUILDPACK_URLを設定することで変更することもできますし、もしも他のバイナリを組み込むbuildpackを作成したならばそれらを複数組み合わせることも可能です。(やりすぎるとSlugサイズはひどいことになるかもしれませんが。。。)

他のバイナリを組み込むbuildpackの作成も数行の変更で可能なのでぜひ試してみてください。

コメント(2)