自作Rubyスクリプトをrubygems、docker imageとして配布、利用するために
問題意識
Ruby で書いた自作のツールを手軽に持ち運んで使いたい。そのために
- rubygems で配布したい(Ruby 環境を持っている場合向け)
- Docker image で配布したい(Ruby 環境を持っていない場合向け)
以上二つを同時に満たしたい。
この辺はまぁずっと持ってる課題意識ではあるし、もう一つのアプローチとしては Wasm もある
Wasmで少しだけ手軽にRubyとRubyスクリプトを持ち運ぶ (2024-05-25) | あーありがち
が、今回はあちこちのクラウドでそのまま runtime として扱いやすい Docker の方の話を扱う。
rubygemsを作る部分は割愛
bundle gem
して雛形を作って、頑張ってコードを書くべし。
docker build時に注意の必要なこと
「持ち込んだもの」は消せない
どういうことかというと、ポイントは以下。
COPY
で*.gem
を「持ち込む」とそのレイヤーが作成される → 消すことができないcurl
などで build プロセス内で「取りに行った」ファイルはその一連の操作を同一のRUN
内に収めてあればレイヤーを残さずに消すことができる
ここで「大した大きさじゃないので気にしない」という判断ができるなら、置き場所さえ気をつければスルーしてもよさそうではある。
「取りに行く」方法の実現方法と妥協点
「じゃあ(容量云々ではなく存在自体が)気になる場合はどうしたらよいのか?」と気になったので、実現方法を考えてみた。
こんな感じ。今回は rubygems の話なので、rubygems 限定で考えると、
- 公開されている rubygems は
rubygems.org/downloads/<name>-<version>.gem
という URL から fetch できる - 公開前の local のものも同じルールに従う URL になっていれば開発中のものをコンテナの中から取りに行くのもそれほど難しくなさそう
Host OSにrubygems.orgっぽいURLを作る
結論コードだけ書くとこんな感じ。
require 'webrick'
Process.daemon(true)
File.open('webrick.pid', 'w') { |f|
server = WEBrick::HTTPServer.new(
StartCallback: -> {
f.puts $$
f.flush
}
)
server.mount('/downloads', WEBrick::HTTPServlet::FileHandler, '<ROOT>/pkg')
server.start
}
bundle gem
で標準的に作られる pkg/
以下を downloads/
と見せるように webrick でサーバを立てる
だけ。pid ファイルを開いて内容を書き込んでいるのは Process.daemon を使って tty から detach したうえで、build 終了後にストップするため。
Dockerコンテナの中からHost OSのURLを叩くには
add-host オプションで build 中のコンテナ内の /etc/hosts にエントリを追加しておく必要があるらしい。
docker build --add-host host:**host-gateway**
これを与えておくと Dockerfile の中で
curl -O http://host/downloads/<name>-<version>.gem
として目的のファイルをダウンロードしてくることができる。
これで開発中のバージョンの gem ファイルをホスト OS から取得して docker image を build することができる。
ただしCI/CD上でホストを見失う
手元でやっている分には dockerd は localhost で動いており、gateway の IP アドレスも分かっているが、「host-gateway
is どこ?」というか、「Host OSはgatewayと同一なの?」な環境で同じことを実現するのは難しい。
今回は
- 未公開の gem を Docker image に収めて公開したいわけじゃない
ということで、CI/CD 上では rubygems.org を参照できるように切り替えることとした。
※ どこか外部のストレージに置いてそのストレージにアクセスしにいければなんでもよいので、S3 などのオブジェクトストレージなんかを利用するのも手だと思うけど、そのためのツールを準備したらその分容量を食うわけで、COPY
の方がマシなケースもそれなりにありそう。
ここまでの実際の全体のコードは以下。
heroku-app-info/bin/build at main · wtnabe/heroku-app-info
今回やりたかったこと
Heroku CLI に丸ごと依存してる gem なので Ruby 環境と Node.js 環境両方必要ということで、
- ruby - Official Image | Docker Hub の slim image に自作の gem を展開
- Herouk CLI は Debian/Ubuntu 用の install script で展開
する方法とした。できたものはそんなたいしたものじゃないし、直接 API 叩けばいいじゃないと思うかもしれないんだけど、PostgreSQL のバージョン情報とか謎の API があって、いったんそこは真面目に作り込むのをやめた。
でもまぁ、こんな感じでも少なくとも自分に都合のいい rubygems を実行するための Docker コンテナを作ることができるのは分かった。あとはこれを定期的に更新しておけば基本的にはオッケー。