【chef】chef-serverでrun_listを利用したserverspecでのテスト

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは植木和樹です。先日chef-soloでのテスト自動化をブログにまとめました。多くの方に関心を持っていただけたようで、苦労して書いた身として大変うれしく思います。

【AWS】JenkinsとserverspecでChefのテストを自動化する

さて本日はその応用、chef-serverを利用した際のテストについて書いてみたいと思います。応用と書きましたが実際やってみたところテストの方法としては全くの別物になりました。なぜchef-soloとchef-serverではテストの方法が異なるのか、についても考えてみたいと思います。

chef-soloとchef-serverについて

「サーバ環境を構築するツール」という観点でchef-soloとchef-serverを比べ、それぞれが使われる規模やサーバの運用状況、テストを実施する際の状況について考えました。

■chef-soloで想定される運用

  • サーバ台数は少ない
  • 一度構築したらあまり更新しない
  • run_listはrecipe単位
  • テストはcookbookを作成後、確認のために実施する

■chef-serverで想定される運用

  • サーバ台数が多い
  • 初期構築後も定期的にchef-clientを実行して環境を同期(収束)し続ける
  • run_listはrole単位
  • テストはcookbookの適用に関わらず、日常的に実施する

chef-soloとchef-serverでは大きく運用の仕方が異なっています。まったくの別物と言っても良いでしょう。さらにchef-soloとchef-serverそれぞれの環境でテストのシナリオを想定し表にまとめてみました。前回ブログのchef-soloは左列でのテストを想定しています。

chefの種類 chef-solo chef-server
テスト対象のサーバは テスト用に新たに起動する すでに起動済み
つまりテスト対象インスタンスが テスト毎に異なる 常に同じサーバ
テスト対象サーバは 1台のみ 複数台
これらのサーバに対して
テストと同時にChef Cookbooksを
適用する 適用しない
適用するCookbooksは 1パターンのみ テストインスタンス毎に異なる
run_listの指定は ノード単位 role単位
chefを実行する際のトリガーは 外部からキックされる ノード自ら実行する
テスト完了後にテスト用インスタンスを 終了する 終了しない

もちろんchef-soloで右列のシナリオができないということではありません。chef-serverで運用する規模で、典型的と思われるテストシナリオとして考えてみました。今回は右のテストシナリオを実現する方法を考えてみたいと思います。

テスト方法のポイント

先にあげた表の中で、今回特に検討しなければならないのは以下の3点です。

  1. テスト対象ノードの台数が「複数台」
  2. テストするCookbooksが「テストインスタンスによって異なる」
  3. run_listの指定はrole単位

つまりテスト実行時に次の処理が必要になります。

  • テスト対象ノードの一覧を取得し
  • ノードに適用されたrun_listを取得し
  • run_listにroleが含まれていればrecipe(cookbook)に展開する

適用されたcookbookに応じたserverspecを実行するRakefile

以上の処理を実現するRakefileは以下になります。chef-soloと大きく異るポイントをハイライトしています。ハイライトされた行それぞれでchef-serverに問い合わせて、情報を取得しています。

前回に続き、多大なるヒント(というかほぼ解決策)をいただいた @kenjiskywalker さんに、この場を借りてお礼を申し上げたいと思います。本当にありがとうございます。【参考サイト】「Chefの中身読んで、外部からrun_listを利用する

なお一点お断りなのですが、動作確認はchef-zeroで行っているため、chef-serverでは異なる部分があるかもしれません。

ファイル:Rakefile

require 'rubygems'
require 'chef'
require 'rspec/core/rake_task'
require 'json'
require 'chef/run_list'

desc "Run serverspec to all hosts"
task :default => 'serverspec:all'

host_run_list = {}

namespace :serverspec do

  Chef::Config[:client_key] = '/etc/chef/client.pem'
  Chef::Config[:chef_server_url] = 'http://chef-server.classmethod.jp/'
  Chef::Config[:node_name] = `hostname`

  host_run_list = Chef::Node.list

  ### start task
  task :all => host_run_list.keys
  host_run_list.keys.each do |target_host|
    ### fetch test node info from chef-server
    node = Chef::Node.find_or_create(target_host)

    desc "Run serverspec to #{target_host}"
    RSpec::Core::RakeTask.new(target_host.to_sym) do |t|
      ENV['TARGET_HOST'] = target_host

      ### fetch run_list of target_host
      target_run_list = node.run_list

      ### expand roles to recipes
      recipes = target_run_list.expand("_default", "server").recipes
      cookbooks = recipes.map { |r| r.sub(/::.+$/, "") }.uniq

      t.pattern = [
        'site-cookbooks/{' + cookbooks.join(',') + '}/spec/serverspec/*_spec.rb',
        "spec/#{target_host}/*_spec.rb"
      ]
    end
  end
end

specディレクトリに作成するspec_helper.rbは前回のchef-soloと同じ内容です。

ファイル:spec/spec_helper.rb

require 'serverspec'
require 'pathname'
require 'net/ssh'
require 'highline/import'
 
include Serverspec::Helper::Ssh
include Serverspec::Helper::DetectOS
 
RSpec.configure do |c|
  c.host  = ENV['TARGET_HOST']
  options = Net::SSH::Config.for(c.host)
  user    = options[:user] || Etc.getlogin
  c.ssh   = Net::SSH.start(c.host, user, options)
  c.os    = backend.check_os
end

Chef::Node.listでノード一覧を取得しています。これはホスト名をキーとしたハッシュで返るので、キーのみを取り出してserverspec内で(ssh)接続しています。前回同様、serverspecを実行するノードから、テスト対象のノードへssh接続ができるよう ~/.ssh/config ファイルを修正しておいてください。

Jenkinsのジョブに登録する場合

テスト環境の前提に書いたとおり、今回のテスト環境ではテスト対象のサーバがすでに稼働していることを想定しています。そのためテスト用EC2インスタンスの起動と終了は不要になるので、ジョブ「01_serverspec-run-instance」と「03_serverspec-destroy-instance」は不要です。

必要なジョブは「02_serverspec-git-clone」のみになりますが、このジョブで実行しているシェルも、テスト用EC2インスタンスの動的IPアドレスを調べる必要がなくなるので、以下のようにとてもシンプルになります。

cd ${WORKSPACE}
rake

最後に

今回はchef-serverを用いた環境でのテストについて考えてみました。chef-serverを使うと、テスト対象ノードの情報すべてをサーバから取得できるので、chef-soloの時のようにaws-cliを駆使する必要がなく非常にシンプルになりました。

テストをする環境やシナリオは現場ごとにそれぞれだと思いますので、これが正しい答えというものはありません。みなさんの環境に応じて見なおしていただければと思います。