Tech Sketch Bucket of Technical Chips by TIS Inc.

TestKitchenとDocker・Serverspecで作るAnsibleTDD環境

Pocket

Ansible playbookの開発を行う際、気軽にトライ&エラー出来る環境があるとサクサクと開発を進めることが出来ます。
そこで、TestKitchenとDocker・Serverspecを使ってAnsibleのTDD(テスト駆動開発)を行う環境を構築する手順を紹介します。

本記事ではUbuntu14,CentOS7でApacheのインストールとサービス起動及びブート時のサービス自動起動を設定するベストプラクティス構成1のAnsible playbook開発を例として取りあげます。

この記事の目標

AnsibleのTDD環境として、TestKitchenで次のことが出来る環境を作ります。

  • Docker上にUbuntu14, CentOS7のコンテナを立ち上げ
  • 各コンテナをAnsibleでプロビジョニング
  • 各コンテナのプロビジョニング結果をServerspecで検証

前提条件

この記事の前提条件は以下のとおりです。

  • 作成するAnsible playbookの構成はベストプラクティスのディレクトリレイアウトに従う1
  • 作業環境は
    • CentOS6.7(x86_64)
    • Rubyインストール済みのこと
    • bundlerインストール済みのこと
    • Dockerインストール済みのこと

なお、この記事の検証に使用した環境は以下のとおりです。

本文

プロジェクト用ディレクトリの作成

プロジェクト用のディレクトリを作成します。これ以降の手順はこのディレクトリをベースに行います。

TestKitchenインストール

プロジェクトディレクトリにGemfileを作成し、bundlerを使ってTestKitchenをインストールします。

書けたらbundle installします。

TestKitchen初期化

kitchen initコマンドでTestKitchenを初期化します。このとき--driver(または-D)オプションでドライバを、--provisioner(または-P)オプションでプロビジョナを指定できます。
今回は、テストを行う環境にDockerを使うのでドライバに"kitchen-docker"、プロビジョニングにはAnsibleを使うのでプロビジョナに"ansible_playbook"をそれぞれ指定します。

初期化を行うと.kitchen.ymlファイルといくつかのディレクトリが作成されます。また、Gemfileが変更されます。

kitchen-ansibleとServerspecインストール

kitchen initによって変更されたGemfileにkitchen-ansibleとserverspecを加え、bundlerを使ってインストールします。

変更したら、もう一度bundle installします。

.kitchen.ymlの設定

kitchen initで作成された.kitchen.ymlは以下のとおりです。これを編集しTestKitchen設定を行います。
(編集が終わった.kitchen.ymlはこのセクションの一番最後にあります。)

driver

driverは、変更する必要はありません。

provisioner

provisionerにはプロビジョナのオプションを以下のように設定します。
なお、ansible_playbookのオプションはProvisioner Optionsも参照してください。

  1. playbook
    master playbook(site.yml)を設定します。
  2. roles_path, group_vars_path, host_vars_path, filter_plugins
    それぞれのディレクトリを相対パスで設定します。
  3. additional_copy_path
    他にテスト環境へコピーするファイル・ディレクトリを相対パスで設定します。この設定は配列なので複数の項目を設定できます。

    ベストプラクティス構成では、サーバー群(tier)ごとのplaybookを作成し、site.ymlでサーバ群ごとのplaybookをincludeすることで、site.ymlをインフラ全体の定義とします。2 しかし、kitchen-ansibleはplaybookに設定したymlをテスト環境へコピーしますが、その中でincludeしているymlまではコピーしてくれません。そこでサーバー群別playbookもテスト環境へコピーされるようadditional_copy_pathに設定します。(rolesディレクトリにあるrole別のplaybookはroles_pathの設定によってコピーされます。)

    今回は、Apacheをインストールしたいので、サーバー群playbookはwebservers.ymlという名前で作ることにして、additional_copy_pathに書いておきます。

  4. hosts
    hostsオプションにはテスト環境がどのホストグループに含まれるホストかを定義します。
    テスト環境にはhostsの設定を元に以下のようなinventryファイルが作成され、プロビジョニングに使用されます。

    今回は、ホストグループもwebserversという名前で作ることにして、とりあえずhostsに書いておきます。

  5. require_ansible_omnibus
    trueにすると、テスト環境へのAnsibleインストールを、ansible_omnibus_urlオプション(デフォルトはansible_install.sh)で定義されたスクリプトで行います。
    このオプションのデフォルトはfalseです。falseの場合、テスト環境へのAnsibleのインストールはyumまたはaptで行うのですが、記事執筆時点ではsyntax errorとなるため、オムニバスインストーラーを使用します。
  6. require_ruby_for_busser
    trueの場合、テスト環境でbusserの実行に使用するrubyをインストールします。(busserはTestKitchenのテストフレームワークです。)

    このオプションのデフォルトはfalseで、その場合はChefをインストールし、それに同梱されているrubyでbusserを実行します。今回はAnsibleを使うためChefは不要、その上rubyよりもインストールに時間がかかるのでtrueにします。

platforms

platformsにはテスト環境を以下のように設定します。

本記事執筆時点のcentos:centos7イメージでは、systemctlを使用するためには特権モードかつ起動コマンドを/sbin/initにしなければならない3ので、そのためのオプションを追加します。

verifier

.kitchen.ymlにverifierを追加しテストツールのオプションを以下のように設定します。

verifierのruby_bindirにはテストツールの実行に使用するrubyがインストールされているディレクトリを設定します。
デフォルトは/opt/chef/embedded/binで、これはChefに同梱されるrubyのディレクトリを指しています。
今回はprovisioner設定でChefを入れず、代わりにrubyをインストールするため、ruby_bindirにはそちらのパスを設定します。

suites

suitesにはテストスイートを以下のように設定します。

run_listはkitchen-ansibleでは使用しないため消してしまいます。
attributesは今回は使用しませんが、extra_varsやtagsを設定する場合はここにぶら下げるので、残しておきます。

以上で、.kitchen.ymlの設定は終わりです。最終的に.kitchen.ymlは以下のようになります。

.kitchen.ymlの編集が終わったら、kitchen listコマンドを叩くと以下のようにテスト環境が表示されるはずです。

テスト環境は[テストスイート名]-[プラットフォーム名]になり、platformとsuiteの組み合わせになります。

Serverspecの初期化

TestKitchenのルール4にしたがって、test/integration/defaultディレクトリへServerspecファイルを配置します。
(test/integration/defaultのdefaultの部分は.kitchen.ymlのsuitesのnameに対応します。テストスイートの名前を変更している場合はServerspecを配置するパスも変更しなければなりません。)

Serverspecの初期化によって作成されたspecディレクトリをserverspecへリネームします。
これは、TestKitchenがテスト環境へインストールするテストランナー(busser)のプラグインをディレクトリ名から決めるためです。ディレクトリ名を変更しない場合busser-specという存在しないプラグインをインストールしようとしてエラーになります。

また、Serverspecの初期化を行うと、サンプルのspecファイルが作成されるので、名前を変更します。

specファイルの中身は以下のとおりで、今回作成したいAnsible playbookにちょうど良いのでそのまま使います。

リネームしたtest/integration/default/serverspecディレクトリにGemfileを追加します。
参考:Rake dependency missing for older rubies · Issue #28

仮のsite.yml作成

以上でTDDの準備が出来ました。と、言いたいところですが、この状態でkitchen testコマンドを叩くとエラーが発生します。

とりあえずエラーを解消するため仮のsite.ymlとwebservers.ymlを作成します。

作成したらもう一度kitchen testを行います。

今度は、default-ubuntu-1404でServerspecによる検証まで行われ、全てfailしました。
TestKitchenはテストが失敗するとそのインスタンスでテストをやめてしまうので、それ以降のインスタンスでテストを行いたい場合は、bundle exec kitchen test default-centos-71のようにインスタンス名を指定します。インスタンス名は正規表現でも指定できるので、条件に合う複数のインスタンスをテストすることも出来ます。

これでようやくTDDの準備が出来ました。
あとは、全てのテストが成功するよう、「書く -> テスト」を繰り返しながらAnsible playbookを書いていけばOKです。

Ansible playbookの実装

TDD環境が出来たので、あとはplaybookを実装していくだけです。
書き方の詳細は本記事の趣旨から外れるのでGoogle先生に聞いてもらうとして、ここには全てのテストが通るplaybookを置いておきます。

これで、kitchen testすれば全てのテストが成功するはずです。

まとめ

AnsibleをTDDするための環境構築の一連の手順を追いかけました。
TestKitchenを使ってトライ&エラーを簡単に行えるTDD環境を用意するとAnsible(やChef)の開発をサクサク進められる用になりますが、デフォルト設定では動かない箇所などハマりどころがいくつかあるので注意が必要です。ハマった場合はログと関連ドキュメントを読んで頑張りましょう。

appendix

Docker imageを指定したい場合

kitchen-docerはKitchen::Driver::Docker.default_imageによってplatform.nameからテスト環境のベースイメージのタグを生成しますが、任意のタグを指定することも出来ます。
その場合は、.kitchen.ymlを以下のように変更します。

複数のホストグループに対応したい場合

playbookにホストグループ(例えば、apservers)を追加定義したい場合は、以下のようにします。

テストスイートではprovisionerのオプションを上書きできるので、テストスイートを増やし、スイートごとにホストグループオプションを設定します。

次にスイートごとのServerspecを用意します。
現在のtestディレクトリは以下のような状態です。

このディレクトリに以下の操作を行います。

  1. defaultディレクトリはテストスイート名なのでこれはwebserversに変更します
  2. Rakefile, Gemfile, spec_helper.rbは複数のテストスイートで共通なので、helpersディレクトリを作りそこへ移動します
  3. webserversディレクトリをコピーしapserversディレクトリとします
  4. コピーしたディレクトリのwebservers_spec.rbをapservers_spec.rbにリネームします。

以上で共通のファイルをhelpersへ移動し、各テストスイートに必要なファイル・ディレクトリを用意できました。あとはapservers_spec.rbファイルをapserversグループに適した形に書き換えれば完了です。

参考資料

エンジニア採用中!私たちと一緒に働いてみませんか?