くりにっき

フルスタックキュアエンジニアです

CIマニアから見た各種CIツールの使い所

社内外でちょいちょい聞かれるのでメモ。

前置き

  • 100%自分の主観なので偏ってます
    • SaaSかオンプレならSaaS派。(自分でサーバの面倒身たくない)
  • 自分が使ったことがないものは紹介していません
    • 今回紹介してるTravis CI, CircleCI, Wercker, GitLab CI, Jenkinsに関しては仕事や趣味で各3〜4年くらいは使ってるはず

GitHubを使ってる場合

ライブラリを作ってる場合

紛らわしいですが https://travis-ci.org/ は無料版で https://travis-ci.com/ が有料版。

今は両方 https://travis-ci.com/ に統一されてるとのこと *1

OSSであれば基本的にはTravis CIを使っていればいいと思います。

Travis CIを選択する理由

2020/4/21追記

CircleCIでマトリクスビルドがサポートされたので一部修正

Travis CIを選択しない理由

アプリを作ってる場合

アプリの場合

  1. テスト
  2. テストが成功したもののみ開発環境にデプロイ
  3. 開発環境で確認して問題がなかったらステージング環境にデプロイ
  4. ステージング環境にデプロイして問題なかったら本番環境にデプロイ

というワークフローがよくあると思いますが、Travis CIだと複雑なワークフローを組むのが大変なので CircleCIWercker を使うのがいいと思っています。

CircleCIとWerckerの共通点

  • 任意のDockerイメージが使える
  • ワークフローが組める
  • 設定ファイルがymlファイル
  • 無料プランでもプライベートリポジトリがビルドできる

CircleCIとWerckerの機能差異

CircleCI 1.0時代はWerckerの方が機能面で優秀でしたが、現時点ではCircleCIの方が全体的に使い勝手がいいと思っています。(今後変わるかも)

理由

  • Werckerは1リポジトリ1 Dockerイメージだが、CircleCIはジョブ単位で利用するDockerイメージを変えられる
  • CircleCIはスケジューラ(cronのようにジョブを定期実行)をサポートしているが、Werckerは非サポート
    • WerckerのAPIをcrontabから定期的に叩けばいけるんだけど、CIサービスだけで完結しないのが難点
    • 【宣伝】Werckerでジョブを定期実行しやすくするツールは昔作りました
  • CircleCIはほぼ全ての設定がymlに集約されているが、Werckerの場合ワークフローの定義部分は管理画面で設定する必要がある
    • リポジトリの管理者しかワークフローの設定をいじれないので、第三者がワークフローの設定変更のPRを送りづらい
  • CircleCIとWerckerではCircleCIの方が複雑なワークフローを組める
  • CircleCIだと自動実行だけでなく、人間が承認することによってワークフローの先のステップに進めるということができる(Manual Approval)

GitLabを使ってる場合

リポジトリ(GitLab本体)と一体化していて連携が楽なのでGitLab CI一択。

設定ファイルの書き方に細かい違いはあるものの、基本的にCircrleCI 2.1相当の機能があるので普通に使う分にはまず機能面で困ることは無いと思います。

GitLab CIの優位点

Jenkinsなどを使った方がいい場合

下記のユースケースに関してはJenkinsや他のツールを使った方がいいと思います

  • 複数のリポジトリにまたがるジョブを構築したい時
    • Travis CI・CircleCI・GitLab CI・Wercker、いずれにせよ複数リポジトリで連携させるジョブを作るのは難しい
  • Parameterized Trigger Plugin のように、ジョブ実行時にパラメータを選択したい時
  • ビルド環境がハードウェア依存の場合
    • 一番分かりやすいのはiOS/Android/Unityアプリなどのビルド
      • ただしCircleCIはiOSビルドをサポートしているし*4、GitLab CIでもMacをRunnerにすることでiOSアプリをビルドすることもできるので、「ハードウェア依存のビルドだからJenkins必須」とは一概には言えない
  • イントラ環境でのビルドが必要な場合
    • SaaSを使いたくない(使えない)という大人の事情以外にも、イントラ内じゃないとライブラリやパッケージがダウンロードできないというのはある
      • ただしそういう時でもCircleCIのEnterpriseを利用するとか、GitLab CIのRunnerをオンプレに置くという方法はある*5

追記:2018/12/8

from id:tnir

CIマニアから見た各種CIツールの使い所 - くりにっき

GitHub\.comを使ってる場合、GitLab CI/CD for GitHub (SaaS)の選択肢はないのかどうか。

2018/12/08 00:01
b.hatena.ne.jp

なるほど。これは知らなかったです

about.gitlab.com

アリかナシかで言えばアリで、GitHubだけどイントラ内でCIをしたい(が自前でJenkinsをホスティングしたくもない)ような場合にはGitLab CIに軍配が上がりそうです。(Runnerをホスティングする手間はあるけど)

*1: https://twitter.com/ndxbn/status/1071045198145191943

*2:TravisだとOracleのインストールだけで10分くらいかかった

*3: https://travis-ci.com/plans

*4:https://circleci.com/docs/2.0/ios-tutorial/

*5:GitLabはそもそもオンプレやんというツッコミはありそうですが、SaaSのGitLab.comとオンプレのRunnerを連携できます

wercker_build_triggerを作った

github.com

wercker_build_triggerについて

Wercker のビルドを外部から手軽に実行するためのツールです

モチベーション

僕は下記のように週1回Travis CIで定期ビルドを実行しています

sue445.hatenablog.com

WerckerでCI回してるリポジトリでも定期ビルドをやりたかったのですが、WerckerではTravis CIのCron jobsのような機能がなかったのでcrontabから実行しやすくするためにツールを作りました。

使い方

golang製なので https://github.com/sue445/wercker_build_trigger/releases からバイナリを落としてきて適当な場所に置いてください。

設定ファイルはこんな感じ

pipelines:
  - application_path: "wercker/docs"
    pipeline_name: "build"
    branch: "master"
  - application_path: "sue445/wercker_build_trigger"

application_pathは必須。pipeline_namebranchは省略可。pipeline_name省略時はbuildが、branch省略時はmaster が使われます

crontabにこんな感じに書いておけば毎週日曜の3:00に実行されます。

0 3 * * 0 /path/to/wercker_build_trigger --config /path/to/wercker_build_trigger.yml --token xxxxxxx

tokenは https://app.wercker.com/profile/tokens で生成したやつを貼り付けてください。

導入事例

実際に使ってる設定ファイル。

pipelines:
  - application_path: "sue445/itamae-plugin-recipe-omori_gohan"
  - application_path: "sue445/itamae-plugin-recipe-tmux"
  - application_path: "sue445/itamae-plugin-recipe-tig"
  - application_path: "sue445/itamae-plugin-recipe-git_now"
  - application_path: "sue445/itamae-plugin-resource-encrypted_remote_file"
  - application_path: "sue445/itamae-plugin-recipe-consul"
  - application_path: "sue445/capistrano-itamae"
  - application_path: "sue445/wercker_build_trigger"

wercker_build_trigger自身の定期ビルドもwercker_build_triggerで行っています。(ややこしい)

実行結果

[application_path:sue445/itamae-plugin-recipe-omori_gohan][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54714816100010bd46f
[application_path:sue445/itamae-plugin-recipe-tmux][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54814816100010bd477
[application_path:sue445/itamae-plugin-recipe-tig][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a5492535040001c453c2
[application_path:sue445/itamae-plugin-recipe-git_now][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54a8e888f0001b7de36
[application_path:sue445/itamae-plugin-resource-encrypted_remote_file][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54c8e888f0001b7de3e
[application_path:sue445/itamae-plugin-recipe-consul][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54d14816100010bd47f
[application_path:sue445/capistrano-itamae][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54e8e888f0001b7de46
[application_path:sue445/wercker_build_trigger][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54f2535040001c453ca

ビルド結果はSlackに流すようにしてるのでこんな風に通知されます

f:id:sue445:20171008131301p:plain

Wercker使っててClassic stackからWorkflowに移行した

以前構築していた Wercker のCI環境をClassic stack(古いCI環境)からworkflow(一番CI環境)に移行したのでメモ

前提

sue445.hatenablog.com

手順

Classic stackからDocker stackを経由してworkflowに移行します。一気にClassicからworkflowまでいけないこともないのですが、Classic stackとDocker stackの互換性がないのと、一度workflowにしたらClassicに戻せないので途中でDocker stackに移行するのがいいと思います

Classic stackからDocker stackに移行

Classic stack だとCI環境に Wercker Registry のboxを使いますが、Docker stackだとDockerのイメージを使うのでそのための移行です

wercker.ymlの差分はこんな感じ

-box: sue445/rvm-vagrant-digitalocean@1.1.1
 +box: drecom/centos-ruby:2.3.1

あとはbox内で行ってたVagrant周りのセットアップをスクリプトに移動してました。

Vagrant込のDockerfile作ってもよかったのですが、あまりオレオレイメージを作りたくなかったので既存のイメージを使う形にしました。(Vagrant本体とVagrantプラグインのインストールで20秒くらいだったのでまあ許容範囲かなと)

ハマったこと1. Vagrant の synced folderでエラーになるようになった

エラーの内容メモるの忘れたので詳しい状況は覚えてないですが、rsyncでエラーになるようになりました(使ってたイメージとの相性?)

調べても長引きそうだったし今回のケースだとローカルからsshごしにitamaeとserverspecを実行する関係でsynced folderは不要だったので無効化して逃げました。。。

     if ENV["WERCKER"] == "true"
+      override.vm.synced_folder ".", "/vagrant", disabled: true
       provider.ssh_key_name = "wercker-#{ENV['WERCKER_GIT_REPOSITORY']}"
       override.ssh.private_key_path = "~/.ssh/id_rsa.vagrant"
     else

ハマったこと2. Dockerの中でネストして bundle exec するとエラーになる

このissueと同じ現象でした

github.com

自分の場合 bundle exec rake itamae:$HOST の中で

sh "bundle exec itamae ssh --host=#{host} --vagrant --node-yaml=recipes/node.yml recipes/install.rb"

のようなrake taskを実行するとvagrantが見つからないみたいなエラーになったので、bundle exec の中で bundle exec を呼ばないようにしました

-      sh "bundle exec itamae ssh --host=#{host} --vagrant --node-yaml=recipes/node.yml recipes/install.rb"
 +      sh "itamae ssh --host=#{host} --vagrant --node-yaml=recipes/node.yml recipes/install.rb"

この時点の差分

https://github.com/sue445/itamae-plugin-resource-encrypted_remote_file/pull/7/files

stackを切り替える

Settings -> Options -> Infrastructure stack よりプルダウンを切り替えてSwitch。

f:id:sue445:20160802015207p:plain

ボタン押すだけなら何か合ってもまだClassicに戻ってこれます

修正をpushする

stackを切り替えた後に修正したやつをpushしないと正常にビルドされないので注意

Docker stackからworkflowに移行

メールフォームから随時申請します

Docker stackを有効にすると上の方にWorkflowsって出てるのでそれをクリック

f:id:sue445:20160802020307p:plain

Request this app to be migrated to workflows のボタンをクリック

f:id:sue445:20160802020405p:plain

メールフォームから申請

UserIDとApplicationIDは入力済みの状態なのであとはメールアドレスを入れるだけ(任意)

f:id:sue445:20160802020613p:plain

メール送ってからWorkflowに移行されるまでに4〜5日くらいかかりました

メールフォームでメールアドレスを書いておけば移行後にメールが送られてくるのですが、メールの文面だけだとどのアプリに対して移行が完了したのか分かりません。

f:id:sue445:20160802020903p:plain

1つだけ申請してるのであればいいのですが、自分の場合5つくらい同時に移行依頼出したのでgmailエイリアスで区別できるようにしました

www.ajaxtower.jp

が、エイリアスつけたメアドには移行完了メールが届かなかったのでダメかもしれない。*1

Workflowの設定

Settings -> Workflow

Pipeline

最初に下の方のPipelineを追加します

f:id:sue445:20160811002149p:plain

YML Pipeline nameがymlのkeyの名前でNameがWerckerで実際に表示される名前(werckerのビルドURLにも使われる)。特にこだわりがなければ両方同じでいいと思います

f:id:sue445:20160811002324p:plain

追加後に Report to SCM にチェックを入れないとPullRequestでビルドステータスが出ません

f:id:sue445:20160811002452p:plain

こういうの

f:id:sue445:20160811002614p:plain

Workflow

[+] の部分をクリックしてポチポチ登録します f:id:sue445:20160811002700p:plain

全てのブランチでビルド実行したければ * でいいと思います。(デプロイだとmaster指定とかになりそう)

f:id:sue445:20160811002819p:plain

Workflowの特徴

  • Workflow内のPipelineがそれぞれ完全に1つのDockerコンテナとして独立している
    • 各Pipelineの冒頭で docker pull してる
    • 1つ目のPipelineでbundle installしてキャッシュを作り、2つ目のPipelineでそのgemを使ってテストを実行というのはできない
    • Pipeline間でステータスを引き継がせる方法はなさそう
  • ymlのマージ機能*2 が使えるので、stepの共通化ができます
default: &default
    after-steps:
        - script:
            name: set variables
            code: |
                # NOTE: override .ruby-version in pretty-slack-notify
                export RBENV_VERSION=2.3.1
        - script:
            name: remove all vms
            code: vagrant destroy -f

        - wantedly/pretty-slack-notify:
            webhook_url: $SLACK_WEBHOOK_URL
            username: wercker_build

build-centos70:
    <<: *default  # 上で定義したafter-stepsが使えるようになる
    steps:
        - script:
            name: setup
            code: ./ci/setup.sh

        - bundle-install:
            jobs: 4

        - script:
            name: build CentOS 7.0
            code: ./ci/build.sh centos70

build-debian8:
    <<: *default  # 上で定義したafter-stepsが使えるようになる
    steps:
        - script:
            name: setup
            code: ./ci/setup.sh

        - bundle-install:
            jobs: 4

        - script:
            name: build Debian 8
            code: ./ci/build.sh debian8

Workflowのメリット

ビルド結果が見やすいってのがあります

それぞれのpipelineのコンソールログが完全に分かれているのでビルドが失敗してもログから調査しやすいです

また、ビルドが失敗した時に任意のpipelineをリトライできるってのも地味に嬉しい

たまたま build-debian8 だけ調子悪くてビルドがこけたので再実行した図

f:id:sue445:20160811003409p:plain

Workflowのデメリット

  • Pipelineの依存関係をウェブでポチる必要があるのが難点
    • GUIで操作できるのは見やすいんですが、個人的にはwercker.ymlで完結してほしかった感はある
  • Pipelineの枝分かれができるのは便利だが、合流することができない
    • 例:枝分かれしたPIpelineが全部終わった時に通知用のPipelineを1つだけ実行

上記デメリットを解消できるという点では個人的には GitLab CIが一番理想に近いと思ってます

tech.drecom.co.jp

まとめ

Dockerfile用意すれば自分の好きな環境を使ってビルドできるし、Travis CIやCircle CIと違って複雑なビルドが書けるのでWercker割とおすすめです

おまけ

移行対象のリポジトリが多すぎて Trello進捗管理していました。便利

f:id:sue445:20160810084301p:plain

1つだけ移行不要にしているのは https://github.com/sue445/wercker-box-rvm-vagrant-digitalocean の動作確認用です

*1:単に一度に申請したのが多すぎただけ説ある

*2: http://magazine.rubyist.net/?0012-YAML#l17

34歳になった&itamaeプラグインを本気でCIする #omotesandorb

自分の誕生日である4/7に表参道.rb #10が開催ということでバースデーLTをしてきました

omotesandorb.connpass.com

例のやつ

www.amazon.co.jp

34歳になりました。もうすぐ定年ですが頑張ります ('A`)

近況

最近はプロビジョニングおじさん業やってます。 itamae, Serverspec, VagrantなどギリギリRubyやってます。

最近送ったPullRequest

vagrant-awsプラグイン(のスポットインスタンス対応版のフォーク)

Fix. Can not used iam_instance_profile options when spot_instance is enabled by sue445 · Pull Request #2 · KariusDx/vagrant-aws · GitHub

スポットインスタンス作成時にIAMロールが適用されなかったので適用されるようにした

vagrant-cloudstackプラグイン(1つ目)

Support multiple network ids by sue445 · Pull Request #148 · schubergphilis/vagrant-cloudstack · GitHub

CloudStackのAPI仕様上は複数network_idを渡せるはずなのにvagrantからだと複数のnetwork_idを渡せないので複数渡せるようにした

vagrant-cloudstackプラグイン(2つ目)

Add ssh_network_id configuration by sue445 · Pull Request #149 · schubergphilis/vagrant-cloudstack · GitHub

複数NICがある場合にeth0以外でsshしたかった

スライド版

LTで発表するためにいろいろ削ったダイジェスト版です

http://sue445.github.io/omotesandorb-10/

https://github.com/sue445/omotesandorb-10/blob/master/slides.md (スライドのソース)

完全版

スライドに書ききれなかった諸々の詳しい説明を含めた完全版です

三行まとめ

itamaeについて

itamaeプラグインについて

レシピ(ミドルウェアのインストール手順など)をgemにしてRubygems.orgで公開することができる

Gemfile

gem "itamae-plugin-recipe-git_now"

recipe.rb

include_recipe "git_now"

git-now がインストールされる

sue445製itamaeプラグイン

暗号化したファイルを転送するプラグイン

sue445.hatenablog.com

tigをビルドするプラグイン

sue445.hatenablog.com

git-nowをビルドするプラグイン

sue445.hatenablog.com

tmuxをビルドするプラグイン

sue445.hatenablog.com

単独にするまでもない雑なレシピ集(今のところdebファイルやrpmファイルをダウンロードしてインストールするレシピだけ)

github.com

itamaeプラグインのテスト事情

https://rubygems.org/search?utf8=%E2%9C%93&query=itamae-plugin

  • 47個中、テストを書いてるgemは18個 *1
    • bundle gem 直後の expect(Itamae::Plugin::Recipe::Hoge::VERSION).not_to be nil しかないやつはノーカン
  • CIしてるgemは5個(全部自分のやつw)
  • itamaeプラグインのIntegration TestのCIの知見を広めたいのが今回の主旨

CIされてることのメリット

  • 複数OSテストしたい時に動作確認が楽(開発者視点)
  • リポジトリのトップにTravis CIとかのバッジが貼ってあれば安心感がある(利用者視点)
    • 常にビルドされているという安心感
    • PR送った時にビルドの結果が出る安心感

f:id:sue445:20160404233427p:plain f:id:sue445:20160404233423p:plain

itamaeプラグインをテストする手順

  1. ローカルでVagrant + VirtualBox環境構築
  2. 自分自身を適用するレシピと、それに対するテストを書く
  3. VirtualBox内でitamaeのレシピ&Serverspec実行
  4. CIでレシピ&Serverspec実行

今回は https://github.com/sue445/itamae-plugin-recipe-git_now で説明

ローカルでVagrant + VirtualBox環境構築

からバイナリをインストール

自分自身を適用するレシピと、それに対するテストを書く

install.rb

include_recipe "git_now"

git_now_spec.rb

describe file("#{node[:git_now][:prefix]}/bin/git-now") do
  it { should be_file }
  it { should be_executable }
end

Serrverspec だとインフラの構成をrspecでテストすることができる

serverspec.org

Serverspecの中でitamaeのnodeを使う方法は下記を参考にしてください

qiita.com

VirtualBox内でitamaeのレシピ&Serverspec実行

itamae実行

f:id:sue445:20160403151948p:plain

Serverspec実行

f:id:sue445:20160403152005p:plain

Vagrantfile

https://github.com/sue445/itamae-plugin-recipe-git_now/blob/v0.1.1/Vagrantfile

Rakefile

itamaeやServerspecの実行はRakefileに集約しておくとCIに落としこむ時に楽です

https://github.com/sue445/itamae-plugin-recipe-git_now/blob/v0.1.1/Rakefile

taskを動的生成してますが rake -T するとこんな感じ

rake itamae:centos70  # Running itamae to centos70
rake itamae:debian8   # Running itamae to debian8
rake spec:centos70    # Run RSpec code examples
rake spec:debian8     # Run RSpec code examples

Rakefile中の

HOSTS = %w(centos70 debian8)

はVagrantfileで使ってるVMに対応しています

https://github.com/sue445/itamae-plugin-recipe-git_now/blob/v0.1.1/Vagrantfile#L25-L41

DigitalOcean

  • 海外の格安VPS
  • 最低プランなら1時間で$0.007から使える
    • 1ドル113円なら0.79円
    • $10のクーポンコードもあるので1428時間は無料で使える
    • https://m.do.co/c/7978f6d6167e から登録してもらえると$10もらえて、$25課金した時に僕も$25もらえます (チラチラッ
  • 全部SSDなので速い
  • ビルドする時だけインスタンスを立ち上げれば費用を抑えることができる

DigitalOceanの設定

Access Tokenの生成

API -> Your Tokens -> Generate New Tokenよりアクセストークンを生成

f:id:sue445:20160331020544p:plain

f:id:sue445:20160331020436p:plain

表示されたトークンは後で使うのでブラウザのタブはそのままにしておく

Wercker

wercker-box-rvm-vagrant-digitalocean

https://github.com/sue445/wercker-box-rvm-vagrant-digitalocean

wercker-box-rvm-vagrant-aws をforkしてrvmとvagrantvagrant-digitaloceanプラグインをインストール済のboxを作った

使い方

wercker.yml

box: sue445/rvm-vagrant-digitalocean@1.0.0

Werckerの設定

Classicにする

Infrastructure stackでClassicを選択 *3

f:id:sue445:20160331020050p:plain

ssh keyの生成

Settings -> SSH keysからDigitalOcean用の鍵を作成 f:id:sue445:20160331015634p:plain

Generateを押すと鍵が生成される f:id:sue445:20160331015818p:plain

Environment variablesの登録

Settings -> Environment variables から環境変数を登録

f:id:sue445:20160331020941p:plain

  • DIGITALOCEAN_ACCESS_TOKEN:さっき作ったDigitalOceanのアクセストークンを入力し、Protectedにチェックを入れる
    • Protectedにチェックが入っていればビルドのコンソールにも出てこなくなる
  • DIGITALOCEAN_KEY:さっき作ったsshの鍵を選択
    • werckerのビルドからは DIGITALOCEAN_KEY_PUBLIC, DIGITALOCEAN_KEY_PRIVATE のような変数で参照できる

wercker.yml(直列実行版)

https://github.com/sue445/itamae-plugin-recipe-git_now/blob/575ef249811ceaeba5d80ecde830c30cea46395c/wercker.yml

wercker.ymlをかいつまんで説明すると

Rubyの設定
        - rvm-use:
            version: 2.3.0

        - script:
            name: install bundler
            code: gem install bundler -v 1.10.6 --no-document

        - bundle-install:
            jobs: 4

bundlerのバージョンが1.10.6なのは、Vagrant *4がbundler 1.11系に対応していないため

https://github.com/mitchellh/vagrant/blob/v1.8.1/vagrant.gemspec#L18

WerckerからDigitalOceanに接続するための設定

上の方で生成した秘密鍵と公開鍵を設置

        - script:
            name: create .ssh directory
            code: mkdir -m 700 -p $HOME/.ssh

        - create-file:
            name: put private key
            filename: $HOME/.ssh/id_rsa.vagrant
            overwrite: true
            hide-from-log: true
            content: $DIGITALOCEAN_KEY_PRIVATE

        - create-file:
            name: put public key
            filename: $HOME/.ssh/id_rsa.vagrant.pub
            overwrite: true
            hide-from-log: true
            content: $DIGITALOCEAN_KEY_PUBLIC

        - script:
            name: chmod 600 id_rsa
            code: chmod 600 $HOME/.ssh/id_rsa.vagrant
ビルド実行
        - script:
            name: test centos70
            code: ./ci/build.sh centos70

        - script:
            name: test debian8
            code: ./ci/build.sh debian8

centos70 -> debian8の順でビルド

build.sh

上の方でRakefileに集約したおかげでビルドスクリプトはシンプルになっています

#!/bin/bash -xe

readonly HOST=$1

vagrant up $HOST --provider=digital_ocean
bundle exec rake itamae:$HOST
bundle exec rake spec:$HOST
vagrant destroy -f $HOST

実はこれだとテストが失敗した時にVMが残り続けるという不具合がありますが後で解決します。(シェルスクリプトの中でエラーハンドリングしてもいいんだけどエラーコードを全部チェックする必要があるのでコードが冗長になる)

ビルドの並列実行

直列実行だと遅い(2つ合わせて5分くらいかかる)ので並列実行できるようにした

Paraduct

  • Paraduct (parallel + parameterize + product)
  • .travis.ymlみたいな感じにいい感じにマトリックステストをするためのgem
  • 2年前に作ったgemなんだけどいろいろ書き直した
    • v0.0.3 -> v1.0.0

詳しい説明

sue445.hatenablog.com

sue445.hatenablog.com

上のエントリだとビルドごとにディレクトリ作ってrsyncするようになってますが、v1.0.0ではrsyncは任意になってます *5

.paraduct.yml

script: |-
  ./ci/build.sh ${HOST}
after_script: |-
  vagrant destroy -f $HOST
variables:
  HOST:
    - debian8
    - centos70
max_threads: 4
  • この例だと ./ci/build.sh debian8./ci/build.sh centos70 が並列に実行される
  • after_script はビルドが失敗時しても必ず実行されるので確実にVMをdestroyしてくれる

その他の特徴

capistranoみたいにホストごとに色がつくのが特徴

f:id:sue445:20160403160203p:plain

https://app.wercker.com/#buildstep/56f46fa951d1ad950a01ad71

CIで使う時

基本的にはビルドスクリプト

gem install paraduct
paraduct test

みたいな処理を書くと思いますが、RubyのプロダクトであればGemfileに

gem "paraduct"

を書いて

bundle exec paraduct test

することでCIサービス側で提供されているbundlerのキャッシュ機能*6を使うことができ、ビルド時間を短縮することができます

小ネタ

参考文献

オライリーのServerspec本にDIgitalOceanやWercker周りの説明が載ってる

www.oreilly.co.jp

itamaeやServerspecの wercker.ymlVagrantfile を熟読した

付録

どうしてTravisCI じゃないのか?

こういうパラメータごとの並列ビルドだとWerckerよりも Travis CIが断然便利なのですが、あえて使わなかった理由は2つあります

  • 有料版の https://travis-ci.com/ だと秘密鍵に対応してるが、無料版の https://travis-ci.org/ だと対応していない *7
    • 管理画面から暗号化したいパラメータを入れる仕組みはあるが、複数行に対応してない
    • 秘密鍵の暗号化してリポジトリに含めつつ(暗号化した時のキーだけ管理画面で保存)暗号化や復号化を自前で頑張ればtravis-ci.org でもたぶんできるんだけど、ビルドスクリプトが複雑になりそうだった
  • Travis CI にはVagrantはインストールされていないのでもし使うとするとビルド中にVagrantVagrantプラグインをインストールする必要があり、その分ビルドに時間がかかる
    • 仮にVagrantのインストールのためにsudoを使おうとすると(.travis.yml で sudo: true を書くと)、コンテナベースの環境を使えないのが痛い。*8
    • 非コンテナは古い形式なので全体的にパフォーマンスが悪い(git pushしてからビルド始まるまでに10分以上かかることもある)
    • 何より非コンテナはbundlerなどのcacheが使えない *9

DigitalOcean vs EC2スポットインスタンス

安いので有名なのはEC2のスポットインスタンスですが、同じくらいの価格帯でスペックを比較してみました *10

DigitalOcean ($ 0.015 / hr) EC2 m1.small ($ 0.01くらい)
Memory 1 GB 1.7 GB
CPU 1 Core 1 Core
Disk 30 GB 160 GB

公式ドキュメント

他のプランだといい感じに値段が同じくらいのがなくて1つずつしか比較できなくて申し訳ないですが、ざっと見た感じ同じくらいの値段ならスポットインスタンスの方がスペックはよかったです

難点は vagrant-awsプラグイン がスポットインスタンスに正式対応してないこと

issueには上がってる

github.com

それでもVagrantからスポットインスタンスを使う方法

さっきのissueで紹介されてるfork版のブランチを使う https://github.com/mitchellh/vagrant-aws/issues/32#issuecomment-163228219

github.com

ググるプラグインを自分でbuildしろみたいなやつが結構ヒットしますが、CIも考慮すると面倒なので自分は素直にGemfileに書いてます

source "https://rubygems.org"

group :development do
  gem "vagrant", github: "mitchellh/vagrant", tag: "v1.8.1"
end

group :plugins do
  # AWSのAPI keyをVagrantfileに環境変数で埋め込んでいるため
  gem "dotenv"
  gem "vagrant-aws", github: "KariusDx/vagrant-aws", branch: "spot"
end

Gemfileに書いてるとvagrantプラグインをforkしてPullRequestする時にも自分のトピックブランチを使えるメリットもあります。(冒頭のPullRequest出してるブランチもGemfileに書いてる)

実行する時

bundle exec vagrant up --provider=aws

最後に

今期のプリキュアRubyがテーマなのでRubyistはみんな見るべき

youtu.be

「魔法つかいプリキュア」なのにRubyスタイルでパワー強化なのはRubyistのマサカリの鋭さの現れか(考えすぎ)

重要なのでもう一度例のやつ

www.amazon.co.jp

*1:2016年4月時点

*2:ただしUnit TestのみCI

*3:wercker-box-rvm-vagrant-digitaloceanはClassicしか対応していないため。Dockerfileでもよかったんだけど強いこだわりはなかったのでサクッといける方を選択(あとitamaeやserverspecがClassic使ってたので準拠)

*4:1.8.1時点

*5:具体的には .paraduct.yml の work_dir を空にすればカレントディレクトリで実行するのでrsyncもしない

*6:TravisCI, Wercker, CircleCIでは使えるのを確認

*7:https://docs.travis-ci.com/user/private-dependencies/ の「Custom SSH keys are currently only available for private repositories on travis-ci.com.」より

*8:https://docs.travis-ci.com/user/workers/container-based-infrastructure/#Routing-your-build-to-container-based-infrastructure

*9:https://docs.travis-ci.com/user/caching/

*10:EC2は東京リージョンでスポットインスタンスの価格は4/7時点のもの

werckerでmavenのpomをキャッシュする方法

werckermavenプロジェクトをビルドすると毎回全部のpomをダウンロードするため、ビルドの時間を短くするために $WERCKER_CACHE_DIR にpomをキャッシュしたかったという話。

mavenはコマンド経由でローカルリポジトリの場所を指定できないみたいなので ~/.m2シンボリックリンクにする方法しか思いつかなかった *1

build:
  steps:
    - script:
        name: setup mvn local repo
        code: |-
          mkdir -p $WERCKER_CACHE_DIR/.m2
          ln -sf $WERCKER_CACHE_DIR/.m2 ~/.m2
    - script:
        name: run test
        code: |-
          mvn test
    - script:
        name: show cache size
        code: |-
          ls -la $WERCKER_CACHE_DIR
          du -sh $WERCKER_CACHE_DIR

*1:~/.m2/settings.xml から/cache を指定するのだとうまくいかなかった