critical alertのブログ

普段はサーバのお世話係をしています

rubyのRPMを作るのをDockerとCircleCIにやらせたら便利だった

この記事はフィードフォースエンジニア Advent Calendar 2015 - Adventar4日目です!

みなさんrubyのRPMビルドってどうしてますか!

CentOSをメインで使っていると最新のrubyはyumで入らないのでどこかのリポジトリからインストールするか自前でビルドする必要があるかと思います。

プロビジョニングのタイミングでrbenvやmakeで入れてもいいのですがciや新規ホストを立てた時など毎回rubyのビルドをするので時間がかかってしまうんですよね。 それが嫌でrubyはリリースされるたびにrpm化してインストールするようにしています。 ところがrpmのビルド環境って用意するのが面倒だったりビルドしたい環境ごとにvagrantを立ち上げて〜とか意外と手間がかかる。

自動化したいなーと思っていて、Dockerは特定の環境を再現するのに最適だし、CircleCIはGitHubのpushやmergeをトリガーにしてあれこれできるし、これだ!っていうことでDockerとCircleCIを使って自動でビルドできるようにしました。

今回の記事で使ったソースコードはこちらです。

critical-alert/oreno-ruby-rpm · GitHub

全体の流れ

circle.ymlを見るとなんとなくわかると思いますが、流れとしては以下のような感じ。

  1. GitHubにpushまたはマージされたタイミングでCircleCIが走る
  2. docker build でdocker imageをビルドする
  3. docker run で実際にRPMビルドを行う
  4. docker cp でコンテナの中から出来たRPMを取り出す

RPMを取り出すと書いていますがこれは正確ではなく、$CIRCLE_ARTIFACTS というパスにファイルを置くとCircleCiのArtifactsからDLできるようになっています。

ポイント

実際にRPMのビルドを行っているのはdocker runで立ち上げたコンテナの中です。 肝心のDockerfileはこのようになっています。

FROM centos:6.6
MAINTAINER hirokazu SUGIUCHI

# setup
RUN yum update -y
RUN yum install -y rpm-build tar

# ruby depends
RUN yum -y install readline-devel ncurses-devel gdbm-devel glibc-devel gcc openssl-devel libyaml-devel libffi-devel zlib-devel

# builduser
RUN useradd rpmbuilder

# build prepare
RUN mkdir -p /home/rpmbuilder/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
RUN chown rpmbuilder:rpmbuilder /home/rpmbuilder/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
WORKDIR /home/rpmbuilder/rpmbuild
ADD http://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.3.tar.gz SOURCES/
COPY ruby22x.spec SPECS/ruby22x.spec
RUN chmod 777 /home/rpmbuilder/rpmbuild/SOURCES/*

# build
USER rpmbuilder
CMD ["rpmbuild", "-ba", "/home/rpmbuilder/rpmbuild/SPECS/ruby22x.spec"]

いくつかポイントがるのでそれぞれ解説します。

rpmをbuildするuserを作った

通常Dockerはすべてrootユーザーでコマンドが実行されますが、rpmをビルドするときはrootでのビルドは推奨されません。(今回に限ればそれほど問題は無いと思いますが) お作法に則ってrpmbuilderという一般ユーザーを作成しています。

Dockerfile内でUSERと指定すると、それ以降はその指定されたユーザーでコマンドが実行されます。 それより前でパーミッションの調整しているのはそのためです。

最後にCMDでrpmbuild

初期の段階では build.sh なるものを作ってそのなかでrubyのソースをダウンロードし、rpmbuildコマンドを実行する、としていました。 そのあとdocker run時に docker run ruby-rpm /bin/sh ./build.sh といったようにshスクリプトを実行させていました。

run時にスクリプトを叩くのはあるあるですが、そのようにしてしまうとそのスクリプトの中であれこれやってしまい何をやっているのかわかりづらくなるのと、スクリプト のメンテも必要になってしまうのでDockerfileで完結するならその方が良いと考えDockerfileのみで完結するようにしました。

Dockerfileの中にCMDがあることによってそのコンテナの役割が明確になるというメリットもあります。 そうすることで、このコンテナはrpmを作るバイナリとして扱えるようになったとも言えます。 (docker imageをrunするだけでrpmがポンとできるような...)

課題

複数のOSに対応していない。今回はCentOS6でビルドしましたが、CentOS7にも対応したいです。 これはDockerfileがDockerfileという名前ではなくてもよくなったことを利用してDockerfileを複数(Dockerfile_centos6とDockerfile_centos7)作り、docker build時に指定しそれぞれ実行すれば作れそうです。

まとめ

  • 特定の環境で実行する処理をDockerで行った
  • GitHubとCircleCIを使うことによって誰でもRPMを作れるようになった
  • Dockerfileを知るいい機会になった

これでrpmのspecファイルとrubyのソースURLをちょいっと変更してpushすればrubyのrpmが出来上がります。 12/25日にはrubyの2.3.0が公開されるはずなのでその時には活躍してもらおうと思っています!

というわけで、5日目の明日はフィードフォースのMaker、kasei_sanです!

参考リンク

大変参考にさせていただきました