rubyのRPMを作るのをDockerとCircleCIにやらせたら便利だった
この記事はフィードフォースエンジニア Advent Calendar 2015 - Adventar4日目です!
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を見るとなんとなくわかると思いますが、流れとしては以下のような感じ。
- GitHubにpushまたはマージされたタイミングでCircleCIが走る
- docker build でdocker imageをビルドする
- docker run で実際にRPMビルドを行う
- 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時に指定しそれぞれ実行すれば作れそうです。
まとめ
これでrpmのspecファイルとrubyのソースURLをちょいっと変更してpushすればrubyのrpmが出来上がります。 12/25日にはrubyの2.3.0が公開されるはずなのでその時には活躍してもらおうと思っています!
というわけで、5日目の明日はフィードフォースのMaker、kasei_sanです!
参考リンク
大変参考にさせていただきました
Chef Meetup(Tech-Circle&CreationLine共催) に行ってきました
Chef Meetup (Tech-Circle&CreationLine共催) - connpass
2015/8/31(月) TIS株式会社 で行われた Chef Meetup(Tech-Circle&CreationLine共催) に行ってきた。 いつもはハンズオン形式らしいのですが今回はセミナー形式での開催とのこと。 質問タイムがほとんどなかったので発表者に質問できるタイミングがあると良かった。(または懇親会的なもの)
1. DevOps, マイクロサービスの米国動向
スライド無し
DevOpsをとりまくdocker,chef,マイクロサービスについての最近の動向についてという内容だった。 chefの話はあんまり出てこず。 dockerは普及のスピードがすごく早いのが特徴的。やはりアプリ層に近いので直接的な効果が大きいのも要因だとか。
コンテナ技術自体は標準化されていっているので、運用周りでビジネスしようとしている。
サービスデリバリーのスピードを獲得するために開発環境で作ったものがそのままコンテナになって本番環境で動くのであれば確かにスピードは速そうとは思ったが、コンテナのスケジューリングとか運用管理が大事よなぁ。
マイクロサービスの話は自分の前提知識の不足と、具体的な活用方法が思い浮かばなくてよくわらかなかった。
2. Chef, Consulを使ったクラウドオーケストレーション
www.slideshare.net
ChefとConsulとありましたがさらにCloudConductorというツールが使われていた。
(自分はAWSのOpsWorksのようなものと理解した)
システムの構成変化をイベントとしてとらえ、 イベントを意識してchefのレシピを分割することで運用フェーズでのクラウドの機能やツールをうまく活用出来る、ということでした。
上記のスライドではアプリケーションコードのdeployまでchef recipeで行っていて、自分は別途deployツールを使用しているので新鮮に感じた。
Consulの使い所としてはchef solo単体では難しい下記を補う用途で使われていた。
- システムの構成管理
- 膨大で不定なノード
- 起動するたびかわるIPアドレス
- サーバー間連携
- 起動しないとわからないパラメーター連携
- サーバーをまたいだレシピの実行順序の制御
- (DB - apacheの順でリスタートしたい、とか)
よく使う機能としては、
- key/value storage
- クラスタ全体で同期されているためノード障害が発生してもデータは保持される
- consul watch
- イベントや通知を受け取ると特定のコマンド、スクリプトを実行
- consul event
- 任意のイベントをクラスタ内に伝播
- 特定ノードのみとかもできる
ただしconsul eventはノードに届く順序は保障されない、先のイベントの処理中でもつぎのイベントを処理してしまう。 順序制御したいのでそのようなツールを作成して使っている。
https://github.com/cloudconductor/metronome
集中管理ではなく自律分散することによってシステム構成が変化するクラウド環境でもうまく自動化できる。
3. Chef12 PremiumAdd-ons について
Chef12になって追加されたPremiumAdd-onsのデモや解説。
- Management Console
- Hosted Chefとほぼ同等のUI
- nodeのattributeやrun_list、cookbookなどがWebUIから閲覧できる
- Reporting
- chef clientの実行履歴(成功、失敗)
- エラー内容、更新されたファイルのdiff
- High Availability
- Replication
- Chef Server apiエンドポイントを複製する機能
- まだRCなので評価できません
- Analytics
- Chef Serverとは別のアプリケーション
- chefの全般のオペレーション解析UI
- 独自ルールを定義し外部に通知できる
- cookbookの変更やchef-client実行などのアクションをルールとして定義できる
- それらをNotifierで通知
- Notifierにはmail,slack連携やwebhookもある
どんな画面なのか気になる人はcreationlineさんのところにいくつかスクリーンショットがあったのでリンクを貼っておく。
Chef Server Enterprise アドオン - CREATIONLINE, INC.
ReportingやAnalyticsあたりはデモもあってすごく便利そうと思ったが、常に自分の頭の中では(でもお高いんでしょう?)という言葉が繰り返されていた。 PremiumAdd-onsなので有料プランだとは思うが値段についてはプレゼンの中での言及はなかった。
公式をみるとこんな感じだった Chef | IT automation for speed and awesomeness | Chef
Chef Server前提だったりAnalyticsはそもそも別のアプリケーションだったりして小規模なシステムだと運用コストに見合わないような印象を受けた。 (小規模ならHosted Chefを使おうということなのかもしれない)
LT#1. Managing Docker hosts in Chef
Chefでdockerをプロビジョニングできるぜ!的な内容でした。
https://supermarket.chef.io/cookbooks/docker
こんなcookbookがあるのを初めて知りましたが、見てみるとなかなかすごいことをしていると思う。
これでコンテナを配置できるようになったけど運用とスケジューリングはどうするの?...っていうオチでした。
まとめ
- Chefの使い方として、ライフサイクルごとに分割してrecipeを書くのは新しい発見だった
- 実際のrecipeが見てみたい
- Chefを単なる自動化のパーツとしてつかう、という感覚はAWSのOpsWorksのようだと思った
- 監視(メトリクス)とかは主題ではなかったためか触れられていなかったが、みんなどうやってるのか気になった
- Consulである程度できたりするのかな?
- Consulの具体的な運用例が聞けてどうやって使っているのかイメージがついた
cookbookを書くときにtest-kitchenを使うと捗る
ちょっとcookbook書いてちゃんと動くか試したいってときにいちいち新しくVagrantfileを用意してnode.json書いて、knife solo cookしてserverspecでテストして...とかやりたいことはrecipeを試したいだけなのに準備が大変なんですよね。
そんなときはその辺を一気に面倒みてくれる便利なtest-kitchenを使ってcookbookの開発をします。
test-kitchenとはchefのcookbookを任意の環境でテストするツールで、vagrantやec2、docker(LXC)上でcookbookを適用し、minitestやserverspecでテストできます。
もとはchefだけでしたが、プラグインを入れることでpuppetやansibleとも連携できるようです。
設定ファイル
test-kitchenの設定ファイルは.kitchen.yml
です。
kitchen initコマンドで生成できます。
$ bundle exec kitchen init
こんな感じのymlが生成されます。
vi .kitchen.yml
--- driver: name: vagrant provisioner: name: chef_solo platforms: - name: ubuntu-12.04 - name: centos-6.4 suites: - name: default run_list: attributes:
設定ファイルの用語
driver
- テストする仮想環境のこと
- vagrant
- EC2
- GCE
- docker 等
provisioner
- chefだけじゃなくansibleとかも使用できる(要追加プラグイン)
- chef-solo
- ansible
- puppet
platforms
suites
- テストスイート
- runlistやattributeを記述する
instance
テストに使うVMはinstance
と呼ばれます。
命名規則は suitesとplatforms に定義された名前になります。
例えばsuitesにapp01
、platformsにcentos6.5
と定義してあるとする。
この場合instance
の名前は app01-centos6.5
となります。
コマンド
$ kitchen create
: インスタンスの起動を行う
$ kitchen setup
: インスタンスにchefとbusserをインストールする
$ kitchen converge
: 収束、なのでchefのrecipeを適用させる
$ kitchen verify
: テストを実行
$ kitchen destroy
: インスタンスを破棄
$ kitchen test
: 上記の全ステップを一気に実行する
テスト
serverspecを使います。testを置くファイルパスは
CHEF_REPO/test/integration/SUITES_NAME/TESTING_FRAMEWORK/
なので、
CHEF_REPO/test/integration/app01/serverspec/
となり、このディレクトリ以下の、*_spec.rb
が読み込まれます。
流れ
全体的な流れは以下のような感じ。 今回はDriverはvagrant、Provisionerはchef-solo、OSはcentos 6.5を使い、テストはserverspecにします。
まずは仮想マシンの準備。
$ vi .kitchen.yml
--- driver: name: vagrant provisioner: name: chef_solo platforms: - name: centos-6.5 suites: - name: default run_list: attributes:
以下のコマンドで状態を確認できます。
$ bundle exec kitchen list Instance Driver Provisioner Last Action default-centos-65 Vagrant ChefSolo <Not Created>
Last Action
でマシンがどんな状態なのが把握できます。
仮想マシンを立ち上げます。
vagrantでいうところのvagrant up
です。
$ bundle exec kitchen create default-centos-65 -----> Starting Kitchen (v1.3.1) -----> Creating <default-centos-65>... Bringing machine 'default' up with 'virtualbox' provider... ==> default: Importing base box 'opscode-centos-6.5'... ==> default: Matching MAC address for NAT networking... ==> default: Setting the name of the VM: default-centos-65_default_1422170919471_91104 中略 Vagrant instance <default-centos-65> created. Finished creating <default-centos-65> (0m57.51s). -----> Kitchen is finished. (0m58.05s)
$ bundle exec kitchen list Instance Driver Provisioner Last Action default-centos-65 Vagrant ChefSolo Created
Last Action
がCreated
になりました。
これで仮想マシンの準備ができたのでrecipeを書きます。
$ vi cookbooks/zsh/recipes/default.rb
package 'zsh'
.kitchen.ymlにrecipeを追記
$ vi .kitchen.yml
--- driver: name: vagrant provisioner: name: chef_solo platforms: - name: centos-6.5 suites: - name: default run_list: - recipe[zsh::default] attributes:
converge でrecipeを仮想マシンに適用させます。chefでいうところのcook
です。
$ bundle exec kitchen converge default-centos-65 -----> Starting Kitchen (v1.3.1) -----> Converging <default-centos-65>... Preparing files for transfer Preparing dna.json Preparing cookbooks from project directory Removing non-cookbook files before transfer Preparing data_bags Preparing environments Preparing roles Preparing solo.rb -----> Installing Chef Omnibus (install only if missing) downloading https://www.chef.io/chef/install.sh to file /tmp/install.sh trying wget... Downloading Chef for el... downloading https://www.chef.io/chef/metadata?v=&prerelease=false&nightlies=false&p=el&pv=6&m=x86_64 to file /tmp/install.sh.2231/metadata.txt trying wget... url https://opscode-omnibus-packages.s3.amazonaws.com/el/6/x86_64/chef-12.0.3-1.x86_64.rpm md5 3634d1a3b6ae2e5977361075da0f44cc sha256 0ec6162b9d0ca2b2016ff02781d84905f712d64c7a81d01b0df88f977832f310 downloaded metadata file looks valid... downloading https://opscode-omnibus-packages.s3.amazonaws.com/el/6/x86_64/chef-12.0.3-1.x86_64.rpm to file /tmp/install.sh.2231/chef-12.0.3-1.x86_64.rpm trying wget... Comparing checksum with sha256sum... Installing Chef installing with rpm... 警告: /tmp/install.sh.2231/chef-12.0.3-1.x86_64.rpm: ヘッダ V4 DSA/SHA1 Signature, key ID 83ef826a: NOKEY 準備中... ########################################### [100%] 1:chef ########################################### [100%] Thank you for installing Chef! Transferring files to <default-centos-65> Starting Chef Client, version 12.0.3 Compiling Cookbooks... Converging 1 resources Recipe: zsh::default - install version 4.3.10-9.el6 of package zsh Running handlers: Running handlers complete Chef Client finished, 1/1 resources updated in 2.158589696 seconds Finished converging <default-centos-65> (2m57.70s). -----> Kitchen is finished. (2m58.26s)
- chefが対象のホストに入っていなければomnibusインストールしてくれます
$ bundle exec kitchen list Instance Driver Provisioner Last Action default-centos-65 Vagrant ChefSolo Converged
Converged
になりました。
次にテストを追加します。
$ mkdir -p test/integration/default/serverspec/
$ vi test/integration/default/serverspec/zsh_spec.rb
require 'serverspec' set :backend, :exec describe package 'zsh' do it { should be_installed } end
verifyでテストを実行します。
$ bundle exec kitchen verify default-centos-65 -----> Starting Kitchen (v1.3.1) -----> Verifying <default-centos-65>... Removing /tmp/busser/suites/serverspec Uploading /tmp/busser/suites/serverspec/zsh_spec.rb (mode=0644) -----> Running serverspec test suite /opt/chef/embedded/bin/ruby -I/tmp/busser/suites/serverspec -I/tmp/busser/gems/gems/rspec-support-3.1.2/lib:/tmp/busser/gems/gems/rspec-core-3.1.7/lib /opt/chef/embedded/bin/rspec --pattern /tmp/busser/suites/serverspec/\*\*/\*_spec.rb --color --format documentation --default-path /tmp/busser/suites/serverspec Package "zsh" should be installed Finished in 0.12178 seconds (files took 0.37162 seconds to load) 1 example, 0 failures Finished verifying <default-centos-65> (0m2.73s). -----> Kitchen is finished. (0m3.23s)
- これはtestディレクトリを対象ホストへアップロード後、busser経由でserverspecをインストールしテストを実行してくれます。
$ bundle exec kitchen list Instance Driver Provisioner Last Action default-centos-65 Vagrant ChefSolo Verified
状態がVerified
になりました!
この後はconverge
とverify
を繰り返してrecipeを作成し、完成したらdestroy
で仮想マシンを破棄します。
$ bundle exec kitchen destroy default-centos-65 -----> Starting Kitchen (v1.3.1) -----> Destroying <default-centos-65>... ==> default: Forcing shutdown of VM... ==> default: Destroying VM and associated drives... Vagrant instance <default-centos-65> destroyed. Finished destroying <default-centos-65> (0m6.84s). -----> Kitchen is finished. (0m7.87s)
よいと思ったところ
- テスト対象のマシンの面倒を見てくれるのが良い
- 設定ファイルが.kitchen.ymlひとつ
- nodes/hostname.json を用意しなくていい
- Vagrantfileを用意しなくていい
- どのdriverやprovisionerを使ってもコマンドは同じ
- 今回はvagrantとchefを利用しましたが、ec2やpuppetを使っても
create
とconverge
- 今回はvagrantとchefを利用しましたが、ec2やpuppetを使っても
あとはこれをそのままjenkinsやCircleCI上で実行できるようにすればcookbook単位のCIも可能ですね。
逆にあるserviceのサーバ構成全体をこれで管理しようとすると、
nodes/hostname.json
と.kitchen.yml
にrecipeの定義が分散してしまって二重管理になりあまり良くない感じでした。
参考にしたもの
rbenvを使うときに入れておくと便利なプラグイン
会社の同僚に教えてもらってこりゃ便利ってなったプラグインたち。
rbenv installしたときにgemを入れてくれるプラグイン
sstephenson/rbenv-default-gems · GitHub
git clone git@github.com:sstephenson/rbenv-default-gems.git ~/.rbenv/plugins/rbenv-default-gems
default-gemsを作って入れたいgemを書いておく
$ vim ~/.rbenv/default-gems bundler pry
rbenvでgemを入れたときにrehashを自動でやってくれるプラグイン
sstephenson/rbenv-gem-rehash · GitHub
$ git clone https://github.com/sstephenson/rbenv-gem-rehash.git ~/.rbenv/plugins/rbenv-gem-rehash
あとはrehashの存在を忘れること。
rbenvとpluginのアップデートをまとめてやってくれるプラグイン
$ git clone https://github.com/rkh/rbenv-update.git ~/.rbenv/plugins/rbenv-update
アップデートするときは以下を実行する。
$ rbenv update