Puppet Advent Calendar 2015の12日目です。
Puppetの性能比較です。9日目に投稿した、OSのRubyと違うバージョンでPuppetを動かす方法でRubyを変えた場合の性能比較。
リソース定義が増えると遅くなる
マニフェストのリソース定義が多すぎると、実行に時間がかかります。なんとなくCPUネックな気がしますが、サーバリソースに余裕がないとかなり遅い場合があります。設定差分チェック、設定変更に時間がかかると使いづらいのでその対策です。
マスター側の対策
マスター/エージェント構成の場合、Puppet3の場合Configuring a Puppet Master Server with Passenger and Apacheを試す、もしくはPuppet4にしてpuppetserver(高速らしい)に乗り換えるなどです。今回こっちは対象外です。
エージェント側の対策
Puppet4のマスター側が速くなったなら、エージェント側も速くなったのか?ということでベンチマークやりました。結果はPuppet4だから速いではなく、エージェント側のRubyをバージョンアップすると速かったとなりました。特に元がRuby1.8.7の場合、2.1.7にすることで実行時間が1/3程度の大幅改善です。(※マニフェスト構成、実行環境によっては違う結果になる可能性もあります。おそらく、インストール処理が大半の場合は短縮しないです)
ベンチマーク
ベンチマーク用マニフェスト
ベンチマークとして、/tmpに10000ファイル作るマニフェストをpuppet applyで適用するテストをしました。ループは使わず、べた書きでリソース定義数は10000です。
ベンチマーク対象
ベンチマーク対象として、次のようなバージョンの組み合わせのDockerイメージをビルドして、ベンチマークやりました。
イメージ | OS | Puppet | Ruby | |
---|---|---|---|---|
1 | CentOS6 (Puppet 3.8.4, Ruby 1.8.7) | CentOS6 | 3.8.4(rpm) | 1.8.7(rpm) |
2 | CentOS6 (Puppet 3.8.4, Ruby 2.1.7) | CentOS6 | 3.8.4(gem) | 2.1.7(rbenv) |
3 | CentOS6 (Puppet 4.3.1, Ruby 2.1.7) | CentOS6 | 4.3.1(rpm) | 2.1.7(puppet-agent) |
4 | CentOS7 (Puppet 3.8.4, Ruby 2.0.0) | CentOS7 | 3.8.4(rpm) | 2.0.0(rpm) |
5 | CentOS7 (Puppet 3.8.4, Ruby 2.1.7) | CentOS7 | 3.8.4(gem) | 2.1.7(rbenv) |
6 | CentOS7 (Puppet 4.3.1, Ruby 2.1.7) | CentOS7 | 4.3.1(rpm) | 2.1.7(puppet-agent) |
補足ですが、
1,4はPuppet3でのrpm最新版の組み合わせです。
3,6はPuppet4のrpm最新版でRubyはpuppet-agentに同梱されているものです。(※Puppet4のrpmで入れるpuppet-agentは、OS側のRubyに依存しない)
2,5はRuby2.1.7をrbenvを使ってバイナリからビルドして、puppetはRuby Gemsで入れています。rpmで入れたPuppetだと、rbenvのRubyを使ってくれないので、rbenvの下のgemパッケージとしてpuppetを入れないとだめでした。最初気づかなくて、Ruby変えても全然速くならないとがっかりしたのは過去のこと。
※最新版は2015/12/5時点。また、Ruby2.2.xはまだ、推奨ではないみたいな記載があったためRuby2.1.7です。
ベンチマーク環境
MacにDocker ToolboxでDocker入れた環境でベンチマークしました。
Mac,docker-machine,dockerと環境表記が難しいので、後の詳細のdockerコンテナのfacterの結果を参考にしてください。
ベンチマーク方法
ベンチマークはDockerコンテナを立てて、1回目のpuppet applyでファイル作成(以降1回目)、2回目のpuppet applyで環境変更なしの場合(以降2回目)、コンテナ停止、コンテナ削除をそれぞれのイメージでやりました。1回目、2回目の実行時間はtimeコマンドのrealの結果です(実行時間は、puppet applyのみの計測)。
なお、ホスト側のバックグランド処理の影響を極力なくすため、各イメージで14回やって、上位2つと下位2つを除いた10回分の平均値をとりました。
ベンチマーク結果
説明が長くなってしまいましたが、ベンチマーク結果です。
横軸が実行時間(秒)で、1st applyが1回目(ファイル作成あり)、2nd applyが2回目(ファイル作成なし)です。結果的には、Rubyのバージョンが新しいほど速いようです。特に今回の結果ではRuby1.8.7はかなり遅い結果でした。
Puppetのバージョンについては、Puppet3.8.4の方が今回わずかに速いですが、この辺はやり方によるかもです。Puppet3.8.4の方は、Ruby2.1.7を実行環境でビルドしているのでその辺の最適化がされている?とか、Puppet4の方がマスター側が高速であれば、トータルではPuppet4の方が速い可能性もあります。その辺は、今回未検証です。あと、OSバージョンについてはほぼ差はないようです。
まとめ
Puppetが遅いと思ったら、Rubyのバージョンアップを検討した方がよいかも。特にRuby1.8系を使っている場合です。
余談
今回は単純リソースを大量につくるだけだったので、moduleが多かったり、Hieraを使ったりすると傾向が違うかもしれません。ちなみに、1ファイルだけ作るマニフェストだと逆にRuby1.8.7の方が速かったりします、この辺はRuby1.9以降の実行時オーバヘッドに絡んでいる???
あと、まとめてから気づきましたが、性能比較としては全部rbenv + Gemsで比較した方がよかったかも。。。
参考
以降、ベンチマークに使ったものとか
詳細を書くと結構大変なので、参考程度にひたすら貼っただけです。
ベンチマーク用マニフェスト作成スクリプト
/tmpの下にファイルをたくさん作る定義をつくる。
#! /usr/bin/evn ruby
def file(no)
index = "%05d"%no
content = "0123456789\\n" * 10
ret = <<EOS
file { '/tmp/benchmark_file_#{index}.txt':
ensure => 'file',
content => "#{content}",
owner => 'root',
group => 'root',
mode => '666',
}
EOS
return ret
end
testcase = [1, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]
testcase.each do |test|
output = "file_%05d.pp"%test
File.open(output, 'w') do |io|
test.times do |no|
io.puts file(no+1)
end
end
end
ベンチマーク環境
facterの抜粋
ホスト依存
memoryfree => 1.03 GB
memorysize => 1.96 GB
processors => {"models"=>["Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz", "Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz", "Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz", "Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz"], "physicalcount"=>1, "count"=>4}
コンテナ側
イメージ名
上記のDockerイメージは、今回は次のようなイメージ名でビルドしました。
イメージ | イメージ名 | |
---|---|---|
1 | CentOS6 (Puppet 3.8.4, Ruby 1.8.7) | local/centos6-puppet |
2 | CentOS6 (Puppet 3.8.4, Ruby 2.1.7) | local/centos6-puppet-ruby-2-1-7 |
3 | CentOS6 (Puppet 4.3.1, Ruby 2.1.7) | local/centos6-puppet-puppet-4-3-1 |
4 | CentOS7 (Puppet 3.8.4, Ruby 2.0.0) | local/centos7-puppet |
5 | CentOS7 (Puppet 3.8.4, Ruby 2.1.7) | local/centos7-puppet-ruby-2-1-7 |
6 | CentOS7 (Puppet 4.3.1, Ruby 2.1.7) | local/centos7-puppet-puppet-4-3-1 |
各コンテナ関連
※facterの表示形式が違うのは、バージョンが違うためです。
local/centos6-puppet
facterversion => 2.4.4
os => {"release"=>{"full"=>"6.7", "minor"=>"7", "major"=>"6"}, "name"=>"CentOS", "family"=>"RedHat"}
osfamily => RedHat
puppetversion => 3.8.4
rubyplatform => x86_64-linux
rubysitedir => /usr/lib/ruby/site_ruby/1.8
rubyversion => 1.8.7
local/centos6-puppet-ruby-2-1-7
facterversion => 2.4.4
os => {"name"=>"CentOS", "family"=>"RedHat", "release"=>{"major"=>"6", "minor"=>"7", "full"=>"6.7"}}
puppetversion => 3.8.4
rubyplatform => x86_64-linux
rubysitedir => /opt/rbenv/versions/2.1.7/lib/ruby/site_ruby/2.1.0
rubyversion => 2.1.7
local/centos6-puppet-puppet-4-3-1
facterversion => 3.1.3
os => {
architecture => "x86_64",
family => "RedHat",
hardware => "x86_64",
name => "CentOS",
release => {
full => "6.7",
major => "6",
minor => "7"
},
selinux => {
enabled => false
}
}
ruby => {
platform => "x86_64-linux",
sitedir => "/opt/puppetlabs/puppet/lib/ruby/site_ruby/2.1.0",
version => "2.1.7"
}
local/centos7-puppet
facterversion => 2.4.4
os => {"name"=>"CentOS", "family"=>"RedHat", "release"=>{"major"=>"7", "minor"=>"1", "full"=>"7.1.1503"}}
puppetversion => 3.8.4
rubyplatform => x86_64-linux
rubysitedir => /usr/local/share/ruby/site_ruby/
rubyversion => 2.0.0
local/centos7-puppet-ruby-2-1-7
facterversion => 2.4.4
os => {"name"=>"CentOS", "family"=>"RedHat", "release"=>{"major"=>"7", "minor"=>"1", "full"=>"7.1.1503"}}
puppetversion => 3.8.4
rubyplatform => x86_64-linux
rubysitedir => /opt/rbenv/versions/2.1.7/lib/ruby/site_ruby/2.1.0
rubyversion => 2.1.7
local/centos7-puppet-puppet-4-3-1
facterversion => 3.1.3
os => {
architecture => "x86_64",
family => "RedHat",
hardware => "x86_64",
name => "CentOS",
release => {
full => "7.1.1503",
major => "7",
minor => "1"
},
selinux => {
enabled => false
}
}
ruby => {
platform => "x86_64-linux",
sitedir => "/opt/puppetlabs/puppet/lib/ruby/site_ruby/2.1.0",
version => "2.1.7"
}
Dockerfile
各イメージビルドしたときに使ったDockerfile
local/centos6-puppet
FROM centos:6
MAINTAINER kijibato
# puppet install
RUN rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm
RUN yum clean all && \
yum install -y puppet && \
yum install -y puppet-server
local/centos6-puppet-ruby-2-1-7
evalの実行があやしいかも?
FROM centos:6
MAINTAINER kijibato
# ----- init
RUN yum clean all
# ----- ruby(2.1.7) install
RUN yum install -y git
RUN yum install -y tar
RUN yum install -y gcc make openssl-devel libffi-devel readline-devel
RUN cd /opt; git clone git://github.com/sstephenson/rbenv.git
RUN mkdir /opt/rbenv/plugins
RUN cd /opt/rbenv/plugins; git clone git://github.com/sstephenson/ruby-build.git
ENV RBENV_ROOT="/opt/rbenv"
ENV PATH="${RBENV_ROOT}/bin:${PATH}"
RUN eval "$(rbenv init -)" && \
rbenv install 2.1.7 && \
rbenv global 2.1.7
# ----- puppet(3.8.4) install from Gems
RUN eval "$(rbenv init -)" && \
rbenv exec gem install puppet -v 3.8.4
local/centos6-puppet-puppet-4-3-1
FROM centos:6
MAINTAINER kijibato
# puppet install
RUN rpm -ivh https://yum.puppetlabs.com/puppetlabs-release-pc1-el-6.noarch.rpm
RUN yum clean all && \
yum install -y puppet-agent && \
yum install -y puppetserver
local/centos7-puppet
FROM centos:7
MAINTAINER kijibato
# puppet install
RUN rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-7.noarch.rpm
RUN yum clean all && \
yum install -y puppet && \
yum install -y puppet-server
local/centos7-puppet-ruby-2-1-7
evalの実行があやしいかも?
FROM centos:7
MAINTAINER kijibato
# ----- init
RUN yum clean all
# ----- ruby(2.1.7) install
RUN yum install -y git
RUN yum install -y tar
RUN yum install -y gcc make openssl-devel libffi-devel readline-devel
RUN cd /opt; git clone git://github.com/sstephenson/rbenv.git
RUN mkdir /opt/rbenv/plugins
RUN cd /opt/rbenv/plugins; git clone git://github.com/sstephenson/ruby-build.git
ENV RBENV_ROOT="/opt/rbenv"
ENV PATH="${RBENV_ROOT}/bin:${PATH}"
RUN eval "$(rbenv init -)" && \
rbenv install 2.1.7 && \
rbenv global 2.1.7
# ----- puppet(3.8.4) install from Gems
RUN eval "$(rbenv init -)" && \
rbenv exec gem install puppet -v 3.8.4
local/centos7-puppet-puppet-4-3-1
FROM centos:7
MAINTAINER kijibato
# puppet install
RUN rpm -ivh https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm
RUN yum clean all && \
yum install -y puppet-agent && \
yum install -y puppetserver
ベンチマークスクリプト
上記のDockerfileでビルドした後に実行しました。
全部流すと結構長いです。もし、使う人がいましたら、利用は自己責任でお願いします。ただ、evalがちょっとあやしいかも?
#! /usr/bin/evn ruby
require 'open3'
require 'pp'
require 'yaml'
test_n = 14
ignore_faster = 2
ignore_later = 2
work = File.expand_path(File.dirname(__FILE__))
ppfile = '/share/file_10000.pp'
images = [
'local/centos6-puppet',
'local/centos6-puppet-ruby-2-1-7',
'local/centos6-puppet-puppet-4-3-1',
'local/centos7-puppet',
'local/centos7-puppet-ruby-2-1-7',
'local/centos7-puppet-puppet-4-3-1'
]
results = {}
images.each do |image|
results[image] = []
end
def get_time(lines)
real = 0
user = 0
sys = 0
lines.each_line do |line|
if /^real (\d+\.\d+)$/ =~ line
real = $1.to_f
end
if /^user (\d+\.\d+)$/ =~ line
user = $1.to_f
end
if /^sys (\d+\.\d+)$/ =~ line
sys = $1.to_f
end
end
return real, user, sys
end
test_n.times do |no|
images.each do |image|
result = {}
puts "----- #{image} "+"-"*25
name = image.gsub(/[\/\-]/, '_')
puts `docker run -d -v #{work}:/share --name #{name} #{image} /sbin/init`
case image
when 'local/centos6-puppet', 'local/centos7-puppet' then
puppet = 'puppet'
when 'local/centos6-puppet-ruby-2-1-7','local/centos7-puppet-ruby-2-1-7' then
puts `docker exec #{name} /bin/bash -c 'eval "$(rbenv init -)"'`
puppet = 'rbenv exec puppet'
when 'local/centos6-puppet-puppet-4-3-1', 'local/centos7-puppet-puppet-4-3-1' then
puppet = '/opt/puppetlabs/bin/puppet'
end
puts '--- 1st apply'
out, err, status = Open3.capture3("docker exec #{name} /bin/bash -c 'time -p #{puppet} apply #{ppfile}'")
puts out
puts err
real, user, sys = get_time(err)
result['1st'] = {'real' => real, 'user' => user, 'sys' => sys}
puts '--- ls'
puts `docker exec #{name} ls -l /tmp`
puts '--- 2nd apply'
out, err, status = Open3.capture3("docker exec #{name} /bin/bash -c 'time -p #{puppet} apply #{ppfile}'")
puts out
puts err
real, user, sys = get_time(err)
result['2nd'] = {'real' => real, 'user' => user, 'sys' => sys}
puts `docker stop #{name}`
puts `docker rm #{name}`
results[image].push(result)
end
end
puts YAML.dump(results)
results.each do |image, result|
puts image + ':'
sort_ret = result.sort{ |a, b| a["1st"]["real"] <=> b["1st"]["real"] }
sort_ret.shift(ignore_faster)
sort_ret.pop(ignore_later)
sum = 0
sort_ret.each do |time|
sum += time['1st']['real']
end
ave = sum / sort_ret.size
puts '+'*10 + "1st(#{sort_ret.size}): #{ave}"
sort_ret = result.sort{ |a, b| a["2nd"]["real"] <=> b["2nd"]["real"] }
sort_ret.shift(ignore_faster)
sort_ret.pop(ignore_later)
sum = 0
sort_ret.each do |time|
sum += time['2nd']['real']
end
ave = sum / sort_ret.size
puts '+'*10 + "2nd(#{sort_ret.size}): #{ave}"
end
おわり。