くりにっき

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

itamaeプラグインのCIを作り直した

個人開発してるitamaeプラグインのCI環境を作り直したのでその知見

tl;dr;

itamaeプラグインのCIにDockerを活用してモダンになった

今までのCI

下記のブログに詳しく書いています

sue445.hatenablog.com

問題点

元々この構成は オライリーのServerspec本 を参考に構築してたのですが、長年使ってきて下記のような問題点がありました

DigitalOcean依存による問題

  • 同一OSでもローカル(VirtualBox)とCI(DigitalOcean)とでイメージが違うので、ローカルだとOKでもCIだとビルドがコケることがある
  • WerckerからDigitalOceanでVMを立ち上げる時に、VMの起動に失敗することが稀によくある
  • ssh越しにitamaeを実行するので遅い
  • DigitalOceanに利用にもお金がかかるのでビルドを回す度にお金が減る
    • itamaeプラグイン6個でweeklyビルドを実行して月1ドル前後

Vagrant依存による問題

  • 古いVagrantに依存しているため最新のbundlerと相性が悪い *1
  • CI実行時に毎回Vagrantをダウンロードしてインストールしてるのでその分ビルドが遅い

新しいCIの構成

  • TravisCI
  • Docker

やったこと

https://github.com/sue445/itamae-plugin-recipe-tig/pull/13 をかいつまんで説明

gemの依存にdocker-apiを追加

spec.add_development_dependency "docker-api"

spec/spec_helper.rbを修正

require 'serverspec'
require 'docker'

set :backend, :docker

set :docker_image, ENV['DOCKER_IMAGE']
set :docker_container, ENV['DOCKER_CONTAINER']

# Disable sudo
# set :disable_sudo, true


# Set environment variables
# set :env, :LANG => 'C', :LC_MESSAGES => 'C'

# Set PATH
# set :path, '/sbin:/usr/local/sbin:$PATH'

# via. http://qiita.com/sue445/items/b67b0e7209a7fae1a52a
require "yaml"
require "itamae/node"

def node
  return @node if @node

  hash = YAML.load_file("#{__dir__}/../recipes/node.yml")

  @node = Itamae::Node.new(hash, Specinfra.backend)
end

自分の場合spec内でnodeの値を使うことが多いので上記のような構成にしてるので不要な場合は node メソッドを削除してください。あとnode.ymlのパスは必要に応じて要微調整。

spec_helper.rbは下記が参考しています

joe-noh.hatenablog.com

.travis.ymlを修正

serviceを使わない場合

envIMAGEでDockerイメージの名前を渡してマトリクステストできるようにしています

language: ruby
sudo: required
services:
- docker
rvm:
- 2.3
env:
- IMAGE=centos:7
- IMAGE=debian:jessie
bundler_args: "--jobs=4"
cache: bundler
before_install:
- gem update --system --no-document
- gem install bundler --no-document
script:
- bundle exec itamae docker --node-yaml=recipes/node.yml recipes/install.rb --image=$IMAGE --tag itamae-plugin:latest
- DOCKER_IMAGE=itamae-plugin:latest bundle exec rspec

serviceを使う場合

CentOS 7だけかもしれないんですが普通に docker run した状態でserviceを起動しようと「Failed to get D-Bus connection: No connection to service manager.」というエラーになるので itamae-plugin-recipe-consul では下記のように書いていました。

before_script:
- docker run --privileged -d --name container-with-service $IMAGE /sbin/init
script:
- bundle exec itamae docker --node-yaml=recipes/node.yml recipes/install.rb --container=container-with-service --tag itamae-plugin:latest
- DOCKER_CONTAINER=container-with-service bundle exec rspec

このエラーの解決には下記が参考になりました

qiita.com

メリット

一番大きなところでは、ビルドの時間が速くなってます。

外部クラウドに対してVM起動することがなくなったり、ssh越しにコマンドを実行せずにローカルのDockerコンテナに対してコマンドを実行するようになったことから全体的にビルド時間が速くなりました

修正前後での1回辺りのビルド時間を集計しましたが、軒並み速くなっているのが確認できます。(修正前後ともに3~5回くらいビルドを実行した時の平均時間を記載)

修正前のビルド時間 (秒) 修正後のビルド時間 (秒)
itamae-plugin-recipe-consul 288.3 197.5
itamae-plugin-recipe-git_now 328.3 248
itamae-plugin-recipe-omori_gohan 300.6 205
itamae-plugin-recipe-tig 374.2 322
itamae-plugin-resource-encrypted_remote_file 441.4 315
itamae-plugin-resource-tmux 612.6 255.75

その他メリット

  • 不安定なビルドがだいぶなくなった
    • 修正前はDigitalOceanのVM起動が不安定だったのですが、TravisCIでDockerを起動することによりビルドがだいぶ安定するようになりました。*2
  • ローカルとCIで完全に同じ環境が使えるようになった
    • ローカルでも --provider=digital_ocean つければいいわけなのですが、自分以外の人がローカルで確認するためだけにDigitalOceanに登録してもらう必要があるので強制しづらい
  • DigitalOceanを使わなくなったのでその分のコストが浮いた

デメリット

  • Docker依存の問題によりレシピが実行できないことがある
    • 前述のsystemd問題など
  • 他にもDocker由来の問題でハマりどころはあるかもしれない

おまけ:同じ構成をCircleCIで書いた場合

最初全く同じ構成をCircleCIで書いていたのですがymlが膨大になりすぎて途中でTravisCIに移行しました。

最初に書いていたCircleCIの設定ファイル

https://github.com/sue445/itamae-plugin-recipe-tmux/blob/fb1ee0273d5445e48c572fa8ac1528907e38b05c/.circleci/config.yml

CircleCIも悪くはないんですが、マトリクステストしたい場合にはTravisCIの方が簡潔に書けていいですね。

*1: https://github.com/hashicorp/vagrant/blob/v1.8.4/vagrant.gemspec#L23

*2:TravisでのDocker起動時にもたまにエラーは出るんだけどDigitalOceanよりはだいぶ頻度は少ない