くりにっき

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

僕がよく使っているalias

はてなブックマークで気になったニュース

を読んで僕がよく使ってるaliasを調べてみました

~/.bash_historyからよく使われるコマンドを集計

$ cat ~/.bash_history | grep -e "^[^#]" | awk '{ print $1 }' | sort | uniq -c | sort -r
208 g
99 t
41 be
29 cd
26 wget
14 gs
13 ag

多い順に出しました。

grep -e "^[^#]" で先頭の # を除外してるのは HISTTIMEFORMAT でコマンドの実行時刻を記録しているためです ref. http://qiita.com/kawaz/items/92457e3d1664383b18bc

  • g
    • git
  • t
    • tig
  • be
    • bundle exec
  • gs
    • git status -sb

https://github.com/sue445/dotfiles

gコマンドのみで集計

gが圧倒的に多かったのでサブコマンドでも集計しました

$ cat ~/.bash_history | grep -e "^g " | awk '{ print $1, $2 }' | sort | uniq -c | sort -r
42 g n
41 g co
28 g push
17 g ci
17 g br
15 g di
10 g fm
7 g ca
6 g sy
  • g n
    • git now --all --stat
    • tmpコミットを作る時によく使います。コミットメッセージは下記のようになります
commit 3ead7875c942d057f6918807210305b717f61403
Author: sue445 <sue445@example.com>
Date: Thu Jun 16 15:18:26 2016 +0900
[from now] 2016/06/16 15:18:26
app/controllers/application_controller.rb | 3 +++
1 file changed, 3 insertions(+)

追伸

このエントリは はてなブックマーク & はてなブログをアプリで使いこなして、はてなTシャツをもらおう! - はてなブックマーク開発ブログ の応募レギュレーションの関係でコマンド実行以外は全部iPhoneで書いています。フリックでmarkdownはきつい、、、

Jenkinsを安全にアップデートする方法

手持ちのJenkinsをいくつかアップデートすることがあったので備忘がてらまとめておきます。*1

Jenkins 1系 -> 2系などの大幅アップデートに限らず、プラグインのアップデートでも使えると思います。

事前にやるべきこと

アップデート後に何か問題があって戻さざるを得ないこともありうるので、すぐに元に戻せる状態にしておきます。*2

Jenkins本体のバックアップ

jenkins.warをそのまま使ってる場合

jenkins.warをどこかにバックアップするか、アップデート前のバージョンをどこかにメモっておいてください。

jenkins.warの過去バージョンは http://mirrors.jenkins-ci.org/war/ からダウンロードできます。

yumやaptを使ってる場合

yum installapt-get install では最新版しかインストールされないので、ロールバックも考慮するとパッケージ指定でインストールした方がいいと思います。

rpmdebはこちらからダウンロードできます

ちなみにパッケージからインストールしておくとjenkinsユーザ作ってくれたりデーモン化までやってくれるのでおすすめです

自分の場合こういうitamaeレシピ書いてます(Debian版)

jenkins.rb

execute "wget http://pkg.jenkins-ci.org/debian/binary/jenkins_#{node[:jenkins][:version]}_all.deb -P #{node[:jenkins][:deb_dir]} && dpkg -i #{node[:jenkins][:deb_dir]}/jenkins_#{node[:jenkins][:version]}_all.deb" do
  not_if "ls #{node[:jenkins][:deb_dir]}/jenkins_#{node[:jenkins][:version]}_all.deb"
end

node.yml

jenkins:
  version: "2.8"
  deb_dir: "/data/backup/jenkins_deb"

コマンドに書き下すとこんな感じ

cd /path/to/backup_dir
wget http://pkg.jenkins-ci.org/debian/binary/jenkins_2.8_all.deb
dpkg -i jenkins_2.8_all.deb

この場合でも戻す場合は過去バージョンのrpmdebでインストールしなおすだけです

最初ymlでバージョン変えてitamae実行したのですが、 /etc/default/jenkinsJENKINS_HOMEJVMのメモリ割り当てで編集してた関係で差分適用するかどうかの入力待ち*3でずっと止まってたので、もしかしたら手動の方がいいかもしれないです。

プラグインや設定一式のバックアップ

手前味噌ですが jenkins-backup-script を使うのがいいです。設定ファイルやプラグイン一式全部バックアップされます。

sue445.hatenablog.com

こういう風にジョブ登録しておいて1日1回夜中に定期実行したり、Jenkins本体やプラグインのアップデート前に手動実行したりしています。

f:id:sue445:20160612141547p:plain

一応tarは外部のバックアップサーバにも転送してるので、突然Jenkinsマシンが死んでもすぐに復旧できる状態にはなってます

Jenkins本体はそうでもないのですが、プラグインは稀に下位互換性をぶっ壊すような変更が入るのでバックアップは必須かと *4

アップデート手順

warなりdebなりrpmなり最新版でインストールし直すだけ。

確認方法

主要なジョブをいくつか手動実行してみる

戻す方法

  • jenkins.warを使ってる場合は過去バージョンのを置き直すだけ
  • rpmdeb使ってる場合は過去バージョンでインストールしなおすだけ
  • jenkins-backup-scriptは下記のようなコマンドで設定ファイルを上書きするだけ
cd /path/to/backup_dir
tar xzvf backup.tar.gz
sudo cp -R jenkins-backup/* /path/to/jenkins/
sudo chown jenkins:jenkins -R /path/to/jenkins/

アップデート時にハマったこと

Branches to build で変数が取れなくなった

jenkins-gitlab-merge-request-builder-plugin を使ってた時にビルド対象のブランチ名がとれなくなったハマりました

設定

f:id:sue445:20160612195822p:plain

正常な時のログ

20:11:03  > git rev-parse refs/remotes/origin/feature/remove_alias_method_chain^{commit} # timeout=10
20:11:03  > git rev-parse refs/remotes/origin/refs/remotes/origin/feature/remove_alias_method_chain^{commit} # timeout=10
20:11:03 Checking out Revision 4e9593d92e293c5ca99eaa6de3b92b973cb1716a (refs/remotes/origin/feature/remove_alias_method_chain)
20:11:03  > git config core.sparsecheckout # timeout=10
20:11:03  > git checkout -f 4e9593d92e293c5ca99eaa6de3b92b973cb1716a

エラー時のログ

19:09:00 Gitlab Merge Request #9222 : xxxx/my_xxxx/feature/update_ruby => master
19:09:01  > git rev-parse refs/remotes/origin/${gitlabSourceBranch}^{commit} # timeout=10
19:09:01  > git rev-parse refs/remotes/origin/refs/remotes/origin/${gitlabSourceBranch}^{commit} # timeout=10
19:09:01  > git rev-parse refs/remotes/origin/${gitlabSourceBranch}^{commit} # timeout=10
19:09:01 ERROR: Couldn't find any revision to build. Verify the repository and branch configuration for this job.

GitLabのwebhookからJenkinsにブランチ名は渡されてるっぽいのだけど、 git rev-parse する時にはブランチ名がとれなくなってるような挙動。

「明示的に設定したパラメータしか変数としてバインドされなくなったのかなー」と思って「ビルドのパラメータ化」のところに gitlabSourceBranch を定義したらビルドが動くようになりました。*5

f:id:sue445:20160612200104p:plain

所感

  • 1系 -> 2系のアップデートで身構えてたけど、アップデート自体は驚くほどサクッとできました
  • パイプラインやJob DSLに全部置き換えるとかだと大変そうだけど、既存のジョブをそのまま移行する分には問題なさそうです
  • とはいえ保険が多いにこしたことはないのでバックアップはしておいた方がいいです

まとめ

  • 何かあった時のロールバック手順は必ず用意しておく(石橋を叩いて渡るくらいが丁度いい)
  • Jenkins 2系はいいぞ

ブコメレス

id:C_L

JenkinsCIををrpmでアップデートすると、$JENKINS_HOMEをchown -Rする糞コマンド入っているので、workspace/以下を綺麗にしておくかexport JENKINS_INSTALL_SKIP_CHOWN=trueしておかないと何時間もupdate終わらんとかになる

ナルホディウス。debだと時間かかっていなかったのでプラットフォームごとに違うのですね。(自分のところのJENKINS_HOME配下もファイル数カオスなので一瞬でchown -Rが終わるとも思えない)

*1:Jenkins 3つ管理してて、それぞれ1.6系から2.8へのアップデート

*2:過去にアップデートしたらダッシュボードが全部吹っ飛んで、バックアップもなかったので脳内ソースを頼りに半日がかりで復旧したことがあります(;´Д`)

*3:上書きしますか?(y/n)的なやつ

*4:http://sue445.hatenablog.com/entry/2016/01/20/232500

*5:リリースノートちゃんと追ってないのでどこで変更あったかは不明

Railsでmysql-clientとmysql-serverのバージョンが食い違ってハマった

GitLab CIでRailsアプリをお手軽CI開発する - Tech Inside Drecom で書ききれなかったおまけです。

tl;dr

あらすじ

GitLab CIでRailsアプリをお手軽CI開発する - Tech Inside Drecom を書いてる時に .gitlab-ci.yml

services:
  - mysql:5.5

って書いていたのになぜか activerecordmysql 5.7以降でしか使えないDDLを発行してmigrationが失敗するという事象が発生。

エラーの状況

migrationファイル

こんな感じのmigrationを実行したかった

class TableRenaemStatusToProfile < ActiveRecord::Migration
  def change
    rename_table :user_statuses, :user_profiles
  end
end

ローカル (MySQL 5.5)

問題なし

Migrating to TableRenaemStatusToProfile (20140126044757)
   (25.6ms)  RENAME TABLE `user_statuses` TO `user_profiles`
   (13.4ms)  CREATE UNIQUE INDEX `index_user_profiles_on_user_id`  ON `user_profiles` (`user_id`)
   (8.7ms)  DROP INDEX `index_user_statuses_on_user_id` ON `user_profiles`

リネーム対象のテーブルにあったindexも新テーブル名に即したindex名に変更された。(activerecord賢い)

GitLab CI

シンタックスエラー(;´Д`)

Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INDEX `index_user_statuses_on_user_id` TO `index_user_profiles_on_user_id`' at line 1: ALTER TABLE `user_profiles` RENAME INDEX `index_user_statuses_on_user_id` TO `index_user_profiles_on_user_id`

user_statusesuser_id というカラムにunique indexが貼られていたため rename_table 内でindex名変更のDDLが発行されていたのですが、MySQL 5.7以降でしか使えない RENAME INDEX が発行されてエラーになってる模様。いやお前5.5だろ。。。

activerecordどの〜!

原因

原因はactiverecordmysql serverのバージョンではなく、Dockerイメージで使ってるlibmysqlclientのバージョンが5.7で、activerecordはそこからMySQLのバージョンを取得していたためでした。*1

ためしにビルドスクリプトデバッグログを出したところ、activerecord上は確かに5.7でした

+ bundle exec rails r 'puts ActiveRecord::Base.connection.send(:full_version)'
5.7.12

今回使ってたDockerのイメージの中を調べたらlibmysqlclientとかのバージョンとも完全一致

$ docker run -i -t drecom/ubuntu-ruby bash
root@8c92dcb22de8:/# dpkg -l | grep mysql
ii  libmysqlclient-dev                   5.7.12-0ubuntu1                     amd64        MySQL database development files
ii  libmysqlclient20:amd64               5.7.12-0ubuntu1                     amd64        MySQL database client library
ii  libqt4-sql-mysql:amd64               4:4.8.7+dfsg-5ubuntu2               amd64        Qt 4 MySQL database driver
ii  mysql-client                         5.7.12-0ubuntu1                     all          MySQL database client (metapackage depending on the latest version)
ii  mysql-client-5.7                     5.7.12-0ubuntu1                     amd64        MySQL database client binaries
ii  mysql-client-core-5.7                5.7.12-0ubuntu1                     amd64        MySQL database core client binaries
ii  mysql-common                         5.7.12-0ubuntu1                     all          MySQL database common files, e.g. /etc/mysql/my.cnf

MySQLのバージョンによるmigrationの挙動の違い

activerecordのソースを読んだところ、MySQL 5.7以降だと RENAME INDEX を使って、MySQL 5.6以下だと CREATE INDEX してから DROP INDEX しているようでした

対処法

冒頭に書いた通りRails 5系では治ってます。Rails 4系でこの問題に直面したら config/initializers/activerecord_mysql_version_patch.rb のようなモンキーパッチを入れれば治ります

# activerecordでクライアントではなくサーバのMySQLのバージョンを使用するためのモンキーパッチ
# via. https://github.com/rails/rails/commit/977ffe880624bbd05f5ee1cc6e4fa51a999884ab

current_version = ActiveRecord.version
if Gem::Version.new("5.0.0.beta1") <= current_version
  raise "This monkeypatch's lifetime is over. Check ActiveRecord version."
end

module ActiveRecord
  module ConnectionAdapters
    class Mysql2Adapter < AbstractMysqlAdapter
      def full_version
        @full_version ||= @connection.server_info[:version]
      end
    end
  end
end

Rails 5系では治っているので、Rails 5系に上げた後にモンキーパッチが残り続けないようにわざとエラーを投げるようにしています。(いわゆる「モンキーパッチの賞味期限」というやつ)

モンキーパッチの賞味期限は下記のスライドが詳しいです

https://speakerdeck.com/eagletmt/activerecord-3-dot-2-4-dot-1?slide=26

まとめ

mysql clientとmysql serverのバージョンが食い違うことは早々ないと思いますが上記のようなやり方で回避は可能です

これはGitLab CI以外にも、Dockerコンテナ採用しているWerckerでも同じ現象が起こりそうな予感。

kamipoさんには足を向けて眠れません m(_ _)m https://github.com/rails/rails/commit/977ffe880624bbd05f5ee1cc6e4fa51a999884ab

*1:activerecord 5系だとmysql serverからバージョンを取得している

【今月のgem】itamae-plugin-recipe-consulを作った

Consul をインストールするためのitamaeプラグインを作りました

github.com

モチベーション

consul自体はgolang製なのでバイナリポン置きでインストールできるのですが、consulのagentをinit.dやsystemdでデーモン化する必要があったのでitamaeプラグインにしました。

OSのバージョンを見てよしなにinit.dスクリプトやsystemdユニットファイルを設置します。CentOS 6, CentOS 7, Debian 8辺りでCIしてます。

consul watchをserviceで起動するレシピも作ったのですが、stretcher辺りと密結合してるので含めていません。(社内レシピには含めてる)

使い方

レシピの中で下記のように書けばOK

include_recipe "consul"

プラグイン内でモジュール化してるので下記のように個別に include_recipe もできます

include_recipe "consul::setup"
include_recipe "consul::install"
include_recipe "consul::service"

node.ymlはこんな感じ

consul:
  # install consul version (required)
  version: "0.6.4"

  # download zip platform (default: "linux_amd64")
  platform: "linux_amd64"

  # path to downloaded zip file (default: "/usr/local/src")
  src_dir: "/usr/local/src"

  # path to consul executable file (default: "/usr/local/src")
  bin_dir: "/usr/local/bin"

  # consul agent -data-dir option (default: "/tmp/consul")
  data_dir: "/tmp/consul"

  # consul agent other options (default: none)
  options: "-server -bootstrap-expect 1"

  gomaxprocs: 2

メリット

serviceにしておくとitamaeやserverspecで

# consul.rb
service "consul" do
  action [:enable, :start]
end
# consul_spec.rb
describe service("consul") do
  it { should be_enabled }
  it { should be_running }
end

のように書けて便利

スペシャルサンクス

consulのinit.dスクリプトとsystemdユニットは下記を参考にさせてもらいました m( )m

qiita.com

追伸

gemをリリースした後で別の人が作った同名のgemを見つけたorz *1 https://github.com/toritori0318/itamae-plugin-recipe-consul

*1:rubygems.orgになければ誰も作ってないと思うじゃないですか。。。

【今月のgem】gemが改ざんされているかどうかチェックするgemを作った

先週のRubygems.orgの脆弱性報告を受けてgemを作りました

github.com

脆弱性の詳細

gemの名前にダッシュが含まれるもので(例:'blank-blank')、2014年6月11日以降、2015年2月8日以前にアップロードされたものは攻撃を受けている可能性があります。(2015年2月8日から2016年4月2日の間にアップロードされたgemはすでに検証済みです。)

とのこと

gemが改ざんされているかどうかチェックするツールとか誰か作ってるだろうと思って探したのですが、Gemfile.lockに含まれてるgemが上記に当てはまるかどうかチェックするスクリプトはありました

https://gist.github.com/yb66/44b97baecbeec6900e039ffb9461d31e

ただ、gem中が改ざんされているかどうかまではチェックしていないし*1、何より自分がリリースしたどのgemをチェックすべきか分からないのでしょうがないので自分で作りました。

gemをたくさんリリースしすぎて手動でチェックするのはむりぽ(´・ω・`) *2

使い方

README に書いてることを日本語で説明を書いただけです

自分がリリースしたgemのうち攻撃を受けた可能性があるgemを検索

自分がowner持ってるgemのうち

  • 2014年6月11日~2015年2月8日にアップロード
  • gem名にダッシュが含まれる

を検索します

$ rubygems_check_replacement_vulnerability vulnerable_gems --username=<USERNAME>

使用例

$ rubygems_check_replacement_vulnerability vulnerable_gems --username=sue445
sue445's vulnerable gems
- faker-precure : 0.0.2, 0.0.3
- fluent-plugin-out_chatwork : 0.0.1, 0.0.2, 0.0.3
- pebbles-tokyu_ruby_kaigi : 0.0.2
- rspec-every_item : 0.0.1
- rspec-parameterized : 0.1.2
- rspec-temp_dir : 0.0.1, 0.0.2, 0.0.3

Rubygems.orgに上がっているgemとリポジトリに上がってるソースコードの差分を調べる

さっき表示されたgemを1つずつ調べます

$ rubygems_check_replacement_vulnerability verify_gem --name=<GEM_NAME> --repo-url=<REPO_URL>

わざわざgem名とリポジトリのURLを手動で入力させているのは、gemspecだけだとリポジトリが特定できないからです。*3

使用例

$ rubygems_check_replacement_vulnerability verify_gem --name=rspec-temp_dir --repo-url=git@github.com:sue445/rspec-temp_dir.git
Unpacked gem: '/var/folders/mx/mmp8n_lx48v8_fr294_zjggw0000gn/T/gem-20160414-51500-dtg1p7/rspec-temp_dir-0.0.1'
[Info] rspec-temp_dir 0.0.1 is safe!
Unpacked gem: '/var/folders/mx/mmp8n_lx48v8_fr294_zjggw0000gn/T/gem-20160414-51500-1hpgj5i/rspec-temp_dir-0.0.2'
[Info] rspec-temp_dir 0.0.2 is safe!
Unpacked gem: '/var/folders/mx/mmp8n_lx48v8_fr294_zjggw0000gn/T/gem-20160414-51500-7aquji/rspec-temp_dir-0.0.3'
[Info] rspec-temp_dir 0.0.3 is safe!

具体的にやってることはREADMEやソース読んでください。

既知の問題

  • 1つリポジトリにgemが複数含まれている場合は考慮できていない
  • rake release 以外でリリースされたgemは考慮できていない
    • gemのバージョンに対応したgitのtagがあること前提なので、 v0.0.1 の形式のtagがないとファイルの比較ができない
    • -> v0.2.0で 0.0.1 のようなvがない形式に対応

まとめ

自分がリリースしたgemはこのgemで全部調べましたが、問題あるのはなかったです。

gemを星の数ほどリリースしてる人は是非ご活用ください

*1:名前とアップロード日時のみチェック

*2:Rubygems,orgに上がってるやつだけで30個、過去バージョンも含めるともっと

*3:homepageは必須ではないので完全ではない

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時点のもの

ZusaarがなくなってもAZusaar!!はなくなりません

表題通りです

サービス終了のお知らせ|参加費の決済もできるイベント開催支援サービス「Zusaar」

Zusaarサービス終了の報を受けてなぜか AZusaar!! も心配されていたのでアナウンスさせていただきます

心配の声

5年前に作って大して更新もしてないサービスに対してこんなに心配してくださるなんてありがたいことです

以下ポエム

AZusaar!!は名前で分かる通り元々はイベント登録サイトの二強の「ATND」と「Zusaar」を一緒に検索するために作ったもので、ZusaarがなければAZusaar!!もなかったし、MashupAwardで入賞することもなかったし、ひいては今の会社に転職もできなかったと思うので*1いろいろと感慨深いものがあります。。。

*1:転職活動の時に自作アプリがMashupAwardで入賞したことを結構アピールしたと思うw