Docker + Serverspecでインフラのテストを行う
knife-soloを使ってサーバー構築しているのだが、serverspecを使ってテストをしていなかった。
でも、前々からしようと思っていたので、今回重い腰を上げて実際にserverspecを使ってテストを行うようにした。
基本的な方針として、ローカル環境では knife-solo + docker を使用しているので、それにあわせて serverspecもknife-soloで作成したコンテナに対して、実行するようにしたい。
ちなみに環境は、
- Mac OSX 10.9
- VirtualBox(Vagrant)にboot2dockerをインストール
Serverspecをインストール
chefのプロジェクトのGemfile に serverspecを追加し、公式サイトに従いインストール。 まあ、この辺は色々と情報があるので、割愛。
また、特に必要と言うわけではないが、~/.ssh/configに dockerを使用するホストのIPアドレス とユーザー名を指定。IPアドレスは環境によって違うので、自分の環境に合わせる。
Host docker HostName 192.168.42.43 User moock
Serverspecをroleに分ける
ローカル環境、ステージング環境、本番環境とあるのでホスト名でディレクトリを分けるのではなく、 ロールで分けるようにした。
この辺は、公式サイトに記述されているように、 以下のようなイメージ。
spec ├── base │ └── base_spec.rb ├── db │ └── mysql_spec.rb ├── solr │ └── solr_spec.rb └── web └── nginx_spec.rb
また、ホストとロールを関連づけるため、以下のようなYAMLファイル (properties.yml) を作成する。
docker: :roles: - base - db - solr - web
そして、作成したテスト実行時に読み込むため、Rakefileに以下を記述する。
require 'rake' require 'rspec/core/rake_task' require 'yaml' properties = YAML.load_file('spec/properties.yml') desc "Run serverspec to all hosts" task :spec => 'serverspec:all' namespace :serverspec do task :all => properties.keys.map {|key| 'serverspec:' + key.split('.')[0] } properties.keys.each do |key| desc "Run serverspec to #{key}" RSpec::Core::RakeTask.new(key.split('.')[0].to_sym) do |t| ENV['TARGET_HOST'] = key t.pattern = 'spec/{' + properties[key][:roles].join(',') + '}/*_spec.rb' end end end
こうすることにより、ホスト毎のテストのタスクが作成される。
$ bundle exec rake -T rake serverspec:docker # Run serverspec to docker rake spec # Run serverspec to all hosts
Serverspecを特定のホストに対して実行する
rakeタスクでserverspecを実行すれば完成なのだが、dockerはコンテナを実行するたびに ポートが変わるので、rakeタスクを実行する際にポートを指定できるように、 spec_helper.rbに、ポートの部分を環境変数で指定するようにする。
require 'serverspec' require 'pathname' require 'net/ssh' include SpecInfra::Helper::Ssh include SpecInfra::Helper::DetectOS RSpec.configure do |c| c.request_pty = true c.host = ENV['TARGET_HOST'] options = Net::SSH::Config.for(c.host) options[:port] = ENV['SSH_PORT'] if ENV['SSH_PORT'] user = options[:user] || Etc.getlogin c.ssh = Net::SSH.start(c.host, user, options) c.os = backend(Serverspec::Commands::Base).check_os end
こうすることにより、
bundle exec rake serverspec:docker SSH_PORT=49153
とsshのポートを指定し、serverspecを実行できるようになる。 これで、dockerとserverspecを実行し、インフラをテストできるようになった。
おまけ
以下のスクリプトを実行すれば、まっさらな状態からインストールが始まり、 テストまで行われ、その後コンテナが廃棄される。
# sshdを起動させ、コンテナを実行 container_id=`docker run -d -p 22 <docker image> /usr/sbin/sshd -D` # dockerのexposeされているsshのポートを取得 ssh_port=`docker inspect --format='{{ if index .NetworkSettings.Ports "22/tcp" }}{{ (index (index .NetworkSettings.Ports "22/tcp") 0).HostPort }}{{ end }}' $container_id` # サーバーのプロビジョン & テスト(dockerは .ssh/config で指定したホスト名) bundle exec knife solo prepare docker -p $ssh_port && bundle exec knife solo cook docker -p $ssh_port && bundle exec rake serverspec:docker SSH_PORT=$ssh_port # コンテナの削除 docker stop $container_id docker rm `docker ps -a -q`
また、これをjenkinsなどに登録し、githubと連携させれば、
chefのコードがプッシュされたときに自動でサーバーの構築され、テストが走るはず。
ためしてないけど。
おしまい。